Skip to content

Commit

Permalink
Update tests to remove reliance on UserDefaults (#156)
Browse files Browse the repository at this point in the history
- Remove all usages of UserDefaults from MapboxSearch and MapboxSearchUI
    - Replaces this with plain dependency injection
- Add LocalhostMockServiceProvider for integration tests to provide a mocked customBaseURL
- Add FeedbackIntegrationTestCase back to UI test suite
- Split MockServerTestCase into UI and Integration variants because it had a namespace collision
- Utilize customBaseURL for ServiceProvider and LocalhostMockServiceProvider
- Stop reading from  "MapboxAPIBaseURL" from NSUserDefaults in `ServiceProvider.createEngine`.
    - Providing a value in Info.plist is still supported.
- Update integration tests to use LocalhostMockServiceProvider
- This fixes a bug in the Demo app where you building and running the app or tests could use the wrong baseApiURL based on the current value in user defaults.
- Update and correct tests for iOS 17 using all mocked data for unit and UI tests.
- Rename `SearchEngine.reverseGeocoding` function to `SearchEngine.reverse`.

- [x] Update `CHANGELOG`

| Before | After |
| -- | -- |
| <img width="355" alt="Screenshot 2024-02-06 at 17 47 49" src="https://github.com/mapbox/mapbox-search-ios/assets/384288/f615ad45-efeb-4687-9373-f89d0037c982"> | <img width="356" alt="Screenshot 2024-02-06 at 17 28 38" src="https://github.com/mapbox/mapbox-search-ios/assets/384288/801d518e-c8c2-4403-93f8-1ef2336338c7"> |
Conflicts:
	CHANGELOG.md
	MapboxSearch.xcodeproj/project.pbxproj
	Sources/MapboxSearch/PublicAPI/ServiceProvider.swift
	Tests/MapboxSearchIntegrationTests/CategorySearchEngineIntegrationTests.swift
	Tests/MapboxSearchIntegrationTests/SearchEngineIntegrationTests.swift
  • Loading branch information
aokj4ck committed Apr 4, 2024
1 parent 500a1e5 commit 9fa46a9
Show file tree
Hide file tree
Showing 28 changed files with 862 additions and 231 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ Guide: https://keepachangelog.com/en/1.0.0/

<!-- Add changes for active work here -->

- [Unit Tests] Update and correct tests for iOS 17 using all mocked data.
- [UI Tests] Update and correct tests for iOS 17 using all mocked data.

- [Search] Rename `SearchEngine.reverseGeocoding` function to `SearchEngine.reverse`.

- [Core] Stop reading "MapboxAPIBaseURL" from UserDefaults in `ServiceProvider.createEngine`. (Providing a value in Info.plist is still supported).

- [Core] Updated to Xcode 14.1 minimum version
- [Core] Updated deployment target to iOS 12
Expand Down
62 changes: 46 additions & 16 deletions MapboxSearch.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion Sources/Demo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
if ProcessInfo.processInfo.arguments.contains("--uitesting") {
ServiceProvider.shared.localFavoritesProvider.deleteAll()
ServiceProvider.shared.localHistoryProvider.deleteAll()
UserDefaults.resetStandardUserDefaults()
}

return true
Expand Down
21 changes: 13 additions & 8 deletions Sources/MapboxSearch/PublicAPI/ServiceProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,21 @@ public typealias FavoritesProvider = LocalDataProvider<FavoriteRecord>
/// Built-in local data provider for ``HistoryRecord`` data.
public typealias HistoryProvider = LocalDataProvider<HistoryRecord>

// MARK: - ServiceProvider

/// Services provider for SearchEngine
public class ServiceProvider: ServiceProviderProtocol {
/// Customize API host URL
/// Customize API host URL with a value from the Info.plist
/// Also supports reading a process argument when in non-Release UITest builds
public static var customBaseURL: String? {
Bundle.main.object(forInfoDictionaryKey: baseURLPlistKey) as? String
#if !RELEASE
if ProcessInfo.processInfo.arguments.contains(where: { $0 == "--uitesting" }) {
let testingBaseUrl = ProcessInfo.processInfo.environment["search_endpoint"]
return testingBaseUrl
}
#endif

return Bundle.main.object(forInfoDictionaryKey: baseURLPlistKey) as? String
}

/// LocalDataProvider for favorites records
Expand Down Expand Up @@ -60,14 +70,9 @@ extension ServiceProvider: EngineProviderProtocol {
accessToken: String,
locationProvider: CoreLocationProvider?
) -> CoreSearchEngineProtocol {
// UserDefaults can be used to setup base url in runtime (e.g. UI tests)
// UserDefaults can be used to setup base url in runtime (e.g. UI tests)
let defaultsBaseURL = UserDefaults.standard.value(forKey: baseURLPlistKey) as? String
let bundleBaseURL = Bundle.main.object(forInfoDictionaryKey: baseURLPlistKey) as? String

let engineOptions = CoreSearchEngine.Options(
accessToken: accessToken,
baseUrl: bundleBaseURL ?? defaultsBaseURL,
baseUrl: Self.customBaseURL,
apiType: NSNumber(value: apiType.rawValue),
userAgent: eventsManager.userAgentName,
eventsUrl: nil
Expand Down
2 changes: 1 addition & 1 deletion Sources/MapboxSearchUI/MapboxSearchController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ extension MapboxSearchController: SearchCategoriesRootViewDelegate {
case .success(let items):
self.delegate?.categorySearchResultsReceived(category: category, results: items)
case .failure(let searchError):
print("Failed search; error=\(searchError)")
_Logger.searchSDK.error("Failed search; error=\(searchError)")
self.presentSearchError(searchError)
}
self.mapboxPanelController?.setState(.collapsed, animated: true)
Expand Down
3 changes: 0 additions & 3 deletions Tests/CI-dev.xctestplan
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"testTargets" : [
{
"skippedTests" : [
"MockServerTestCase",
"OfflineIntegrationTests"
],
"target" : {
Expand All @@ -43,7 +42,6 @@
},
{
"skippedTests" : [
"MockServerTestCase"
],
"target" : {
"containerPath" : "container:MapboxSearch.xcodeproj",
Expand All @@ -55,7 +53,6 @@
"enabled" : false,
"skippedTests" : [
"BaseTestCase",
"MockServerTestCase"
],
"target" : {
"containerPath" : "container:MapboxSearch.xcodeproj",
Expand Down
7 changes: 2 additions & 5 deletions Tests/Demo.xctestplan
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"testTargets" : [
{
"skippedTests" : [
"MockServerTestCase"
"MockServerIntegrationTestCase"
],
"target" : {
"containerPath" : "container:MapboxSearch.xcodeproj",
Expand All @@ -44,9 +44,6 @@
}
},
{
"skippedTests" : [
"MockServerTestCase"
],
"target" : {
"containerPath" : "container:MapboxSearch.xcodeproj",
"identifier" : "3A0D7E54233522D4006D81BB",
Expand All @@ -56,7 +53,7 @@
{
"skippedTests" : [
"BaseTestCase",
"MockServerTestCase"
"MockServerUITestCase"
],
"target" : {
"containerPath" : "container:MapboxSearch.xcodeproj",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,30 @@ import CoreLocation
@testable import MapboxSearch
import XCTest

final class AddressAutofillIntegrationTests: MockServerTestCase {
final class AddressAutofillIntegrationTests: MockServerIntegrationTestCase {
private var addressAutofill: AddressAutofill!
private let locationProvider = WrapperLocationProvider(wrapping: DefaultLocationProvider())

override func setUp() {
super.setUp()

addressAutofill = AddressAutofill(
let reporter = CoreUserActivityReporter.getOrCreate(
for: CoreUserActivityReporterOptions(
accessToken: "access-token",
userAgent: "mapbox-search-ios-tests",
eventsUrl: nil
)
)

let engine = LocalhostMockServiceProvider.shared.createEngine(
apiType: CoreSearchEngine.ApiType.autofill,
accessToken: "access-token",
locationProvider: DefaultLocationProvider()
locationProvider: locationProvider
)

addressAutofill = AddressAutofill(
searchEngine: engine,
userActivityReporter: reporter
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@ import CoreLocation
@testable import MapboxSearch
import XCTest

class CategorySearchEngineIntegrationTests: MockServerTestCase {
lazy var searchEngine = CategorySearchEngine(
accessToken: "access-token",
locationProvider: DefaultLocationProvider(),
supportSBS: true
)
final class CategorySearchEngineIntegrationTests: MockServerIntegrationTestCase {
private var searchEngine: CategorySearchEngine!

override func setUp() {
super.setUp()

searchEngine = CategorySearchEngine(
accessToken: "access-token",
serviceProvider: LocalhostMockServiceProvider.shared,
supportSBS: true
)
}

func testCategorySearch() throws {
try server.setResponse(.categoryCafe)

let expectation = XCTestExpectation(description: "Expecting results")
searchEngine.search(categoryName: "ATM") { result in
searchEngine.search(categoryName: "cafe") { result in
switch result {
case .success(let searchResults):
XCTAssertFalse(searchResults.isEmpty)
Expand All @@ -30,7 +36,7 @@ class CategorySearchEngineIntegrationTests: MockServerTestCase {
try server.setResponse(.categoryCafe, statusCode: 500)

let expectation = XCTestExpectation(description: "Expecting failure")
searchEngine.search(categoryName: "ATM") { result in
searchEngine.search(categoryName: "cafe") { result in
switch result {
case .success:
XCTFail("Not expected")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@testable import MapboxSearch
import XCTest

class LocalhostMockServiceProvider: ServiceProviderProtocol {
public static var customBaseURL: String?

/// LocalDataProvider for favorites records
public let localFavoritesProvider = FavoritesProvider()
/// LocalDataProvider for history records
public let localHistoryProvider = HistoryProvider()
/// MapboxMobileEvents manager for analytics usage
public let eventsManager = EventsManager()

/// Responsible for sending feedback related events.
public private(set) lazy var feedbackManager = FeedbackManager(eventsManager: eventsManager)

/// Shared instance of ServiceProvider
public static let shared = LocalhostMockServiceProvider()

var dataLayerProviders: [IndexableDataProvider] { [localHistoryProvider, localFavoritesProvider] }
}

extension LocalhostMockServiceProvider: EngineProviderProtocol {
func getStoredAccessToken() -> String? {
Bundle.main.object(forInfoDictionaryKey: accessTokenPlistKey) as? String
}

func createEngine(
apiType: CoreSearchEngine.ApiType,
accessToken: String,
locationProvider: CoreLocationProvider?
) -> CoreSearchEngineProtocol {
let engineOptions = CoreSearchEngine.Options(
accessToken: accessToken,
baseUrl: Self.customBaseURL,
apiType: NSNumber(value: apiType.rawValue),
userAgent: "mapbox-search-ios-tests",
eventsUrl: nil
)

return CoreSearchEngine(
options: engineOptions,
location: locationProvider
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import CoreLocation
@testable import MapboxSearch
import XCTest

class MockServerTestCase: XCTestCase {
class MockServerIntegrationTestCase: XCTestCase {
let server = MockWebServer()

func setServerResponse(_ response: MockResponse, query: String? = nil) throws {
Expand All @@ -12,7 +12,7 @@ class MockServerTestCase: XCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()

UserDefaults.standard.setValue(server.endpoint, forKey: "MapboxAPIBaseURL")
LocalhostMockServiceProvider.customBaseURL = server.endpoint

try server.start()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import MapboxCommon
@testable import MapboxSearch
import XCTest

class OfflineIntegrationTests: MockServerTestCase {
class OfflineIntegrationTests: MockServerIntegrationTestCase {
let delegate = SearchEngineDelegateStub()
let searchEngine = SearchEngine()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,29 @@ import CoreLocation
@testable import MapboxSearch
import XCTest

final class PlaceAutocompleteIntegrationTests: MockServerTestCase {
final class PlaceAutocompleteIntegrationTests: MockServerIntegrationTestCase {
private var placeAutocomplete: PlaceAutocomplete!

override func setUp() {
super.setUp()

placeAutocomplete = PlaceAutocomplete(
let reporter = CoreUserActivityReporter.getOrCreate(
for: CoreUserActivityReporterOptions(
accessToken: "access-token",
userAgent: "mapbox-search-ios-tests",
eventsUrl: nil
)
)

let engine = LocalhostMockServiceProvider.shared.createEngine(
apiType: CoreSearchEngine.ApiType.SBS,
accessToken: "access-token",
locationProvider: DefaultLocationProvider()
locationProvider: WrapperLocationProvider(wrapping: DefaultLocationProvider())
)

placeAutocomplete = PlaceAutocomplete(
searchEngine: engine,
userActivityReporter: reporter
)
}

Expand All @@ -21,7 +35,7 @@ final class PlaceAutocompleteIntegrationTests: MockServerTestCase {
try server.setResponse(.retrieveSanFrancisco)

var suggestion: PlaceAutocomplete.Suggestion?
placeAutocomplete.suggestions(for: "query") { result in
placeAutocomplete.suggestions(for: "San Francisco") { result in
switch result {
case .success(let suggestions):
XCTAssertEqual(suggestions.count, 10)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import CoreLocation
@testable import MapboxSearch
import XCTest

class SearchEngineGeocodingIntegrationTests: MockServerIntegrationTestCase {
let delegate = SearchEngineDelegateStub()
var searchEngine: SearchEngine!

override func setUp() {
super.setUp()

searchEngine = SearchEngine(
accessToken: "access-token",
serviceProvider: LocalhostMockServiceProvider.shared,
locationProvider: DefaultLocationProvider(),
supportSBS: false
)

searchEngine.delegate = delegate
}

func testReverseGeocodingSearch() throws {
try server.setResponse(.reverseGeocoding)

let expectation = XCTestExpectation()
let options = ReverseGeocodingOptions(point: CLLocationCoordinate2D(latitude: 12.0, longitude: 12.0))

// Search engine is a SBS type!
searchEngine.reverseGeocoding(options: options) { result in
if case .success(let reverseGeocodingResults) = result {
XCTAssertFalse(reverseGeocodingResults.isEmpty)
} else {
XCTFail("No resolved result")
}
expectation.fulfill()
}

wait(for: [expectation], timeout: 10)
}

func testReverseGeocodingSearchFailed() throws {
try server.setResponse(.reverseGeocoding, statusCode: 500)

let expectation = XCTestExpectation()
let options = ReverseGeocodingOptions(point: CLLocationCoordinate2D(latitude: 12.0, longitude: 12.0))
searchEngine.reverseGeocoding(options: options) { result in
if case .failure(.reverseGeocodingFailed(let reasonError as NSError, _)) = result {
XCTAssert(reasonError.code == 500)
expectation.fulfill()
}
}
wait(for: [expectation], timeout: 10)
}
}
Loading

0 comments on commit 9fa46a9

Please sign in to comment.