Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize test mocks #179

Merged
merged 21 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6cb7255
Fix typo
aokj4ck Feb 13, 2024
92a19ca
Move MockResponse to protocol, add httpMethod and path fields
aokj4ck Feb 14, 2024
b508e71
Change MockServerIntegrationTestCase and MockWebServer to generics co…
aokj4ck Feb 14, 2024
e992fa1
Start implementing AutofillMockResponse for integration tests with Au…
aokj4ck Feb 14, 2024
70bd1be
Add SBSMockResponse
aokj4ck Feb 14, 2024
3b246c9
Rename LegacyResponse to GeocodingMockResponse
aokj4ck Feb 14, 2024
59fa6aa
Add MockResponse generic to MockServerUITestCase
aokj4ck Feb 14, 2024
5a59910
Disable OfflineIntegrationTests until later
aokj4ck Feb 14, 2024
fdb0d19
Change CI test frameworks step to ci-full-test which adds UI tests
aokj4ck Feb 14, 2024
00b7474
Pare down GeocodingMockResponse, remove mocks that are not geocoding …
aokj4ck Feb 14, 2024
2fe6b25
Add mark declarations to MockResponse file
aokj4ck Feb 14, 2024
906a214
Pare down SBSMockResponse to remove unused mocks
aokj4ck Feb 14, 2024
5f112f6
Merge branch 'main' of github.com:mapbox/mapbox-search-ios into gener…
aokj4ck Feb 15, 2024
c83e0a5
Update changelog
aokj4ck Feb 15, 2024
63ff493
Merge branch 'main' of github.com:mapbox/mapbox-search-ios into gener…
aokj4ck Feb 15, 2024
38be4db
Merge branch 'main' of github.com:mapbox/mapbox-search-ios into gener…
aokj4ck Feb 15, 2024
3511414
Update visibility UI tests to use network Mocks
aokj4ck Feb 15, 2024
5aa4242
Restore Package.resolved file
aokj4ck Feb 15, 2024
051ef74
Update docs for CoreSearchEngine.ApiType.toSDKType (tests only)
aokj4ck Feb 15, 2024
71b0fd5
Update for PR feedback: add typealias for MockSBSServerUITestCase, re…
aokj4ck Feb 16, 2024
3629b6f
Merge branch 'main' into generalize-test-mocks
aokj4ck Feb 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ jobs:
command: make dependencies
- run:
name: Test frameworks
command: make ci-dev-test
command: make ci-full-test
- run: make codecov
- run:
name: Prepare artifacts
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Guide: https://keepachangelog.com/en/1.0.0/

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

- [Tests] Change MockResponse into a protocol, create separate enums conforming to MockResponse for each API type (geocoding, sbs, autofill), add MockResponse as generic to each test base class and MockWebServer.

- [Discover] Add support for country, proximity, and origin parameters in Discover.Options search parameters. This fixes an issue when using search-along-route to query category results.

- [SearchUI] Add `distanceFormatter` field to Configuration to support changing the search suggestions distance format. Nil values will use the default behavior.
Expand Down
8 changes: 8 additions & 0 deletions MapboxSearch.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
042477C62B72CCB000D870D5 /* geocoding-reverse-geocoding.json in Resources */ = {isa = PBXBuildFile; fileRef = 042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */; };
042477C72B72CCB000D870D5 /* geocoding-reverse-geocoding.json in Resources */ = {isa = PBXBuildFile; fileRef = 042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */; };
043A3D4D2B30F38300DB681B /* CoreAddress+AddressComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043A3D4C2B30F38300DB681B /* CoreAddress+AddressComponents.swift */; };
045514C22B7D4B58000D88B9 /* CoreApiType+ToSDKType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 045514C12B7D4B58000D88B9 /* CoreApiType+ToSDKType.swift */; };
045514C32B7D4B58000D88B9 /* CoreApiType+ToSDKType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 045514C12B7D4B58000D88B9 /* CoreApiType+ToSDKType.swift */; };
045514C42B7D4B58000D88B9 /* CoreApiType+ToSDKType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 045514C12B7D4B58000D88B9 /* CoreApiType+ToSDKType.swift */; };
048823482B6B0A9D00C770AA /* category-hotel-search-along-route-jp.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B7C2B6B043C00FDE7D5 /* category-hotel-search-along-route-jp.json */; };
048823492B6B0A9D00C770AA /* category-hotel-search-along-route-jp.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B7C2B6B043C00FDE7D5 /* category-hotel-search-along-route-jp.json */; };
0488234A2B6B0A9E00C770AA /* category-hotel-search-along-route-jp.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B7C2B6B043C00FDE7D5 /* category-hotel-search-along-route-jp.json */; };
Expand Down Expand Up @@ -498,6 +501,7 @@
042477C12B7290E700D870D5 /* SearchEngineGeocodingIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchEngineGeocodingIntegrationTests.swift; sourceTree = "<group>"; };
042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "geocoding-reverse-geocoding.json"; sourceTree = "<group>"; };
043A3D4C2B30F38300DB681B /* CoreAddress+AddressComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreAddress+AddressComponents.swift"; sourceTree = "<group>"; };
045514C12B7D4B58000D88B9 /* CoreApiType+ToSDKType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreApiType+ToSDKType.swift"; sourceTree = "<group>"; };
04AB0B792B6AF37800FDE7D5 /* DiscoverIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverIntegrationTests.swift; sourceTree = "<group>"; };
04AB0B7C2B6B043C00FDE7D5 /* category-hotel-search-along-route-jp.json */ = {isa = PBXFileReference; explicitFileType = text.json; path = "category-hotel-search-along-route-jp.json"; sourceTree = "<group>"; };
04970F8C2B7A97C900213763 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1458,6 +1462,7 @@
children = (
FEEDD3882508E24900DC0A98 /* LotLan+Extensions.swift */,
F970FAD7252B14950000806B /* SearchResultType+Extensions.swift */,
045514C12B7D4B58000D88B9 /* CoreApiType+ToSDKType.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -2615,6 +2620,7 @@
F953A49325ECEB60003A3681 /* SearchCategorySuggestion+Samples.swift in Sources */,
F9F882962583779A00062A82 /* CoreImageInfoStub.swift in Sources */,
FEA92E712564149100427965 /* CoreSuggestAction+Samples.swift in Sources */,
045514C32B7D4B58000D88B9 /* CoreApiType+ToSDKType.swift in Sources */,
2CA1E22129F09CD200A533CF /* PlaceAutocomplete.Suggestion+Tests.swift in Sources */,
FED7E1A825517C5100DA34D9 /* CoreBoundingBoxTests.swift in Sources */,
F9D70E5C252740DD0019105F /* IndexableDataProviderTests.swift in Sources */,
Expand Down Expand Up @@ -2663,6 +2669,7 @@
F9201ECA253EECE0002D141B /* CategorySuggestionsIntegrationTestCase.swift in Sources */,
F907443B261B13F40091899C /* MockServerUITestCase.swift in Sources */,
F9ACA6132642B66600F50CD4 /* MockWebServer.swift in Sources */,
045514C42B7D4B58000D88B9 /* CoreApiType+ToSDKType.swift in Sources */,
FEEDD3D22508E45B00DC0A98 /* VisibilityTestCase.swift in Sources */,
F9AB03C3261DBD7F00EDF1E7 /* MockResponse.swift in Sources */,
FEEDD3CE2508E45B00DC0A98 /* BaseTestCase.swift in Sources */,
Expand All @@ -2678,6 +2685,7 @@
files = (
F9C557B62670CC4500BE8B94 /* SearchEngineDelegateStub.swift in Sources */,
F9C9461B2743CB1700763F2C /* TestTileStore.swift in Sources */,
045514C22B7D4B58000D88B9 /* CoreApiType+ToSDKType.swift in Sources */,
F9C557C12670CD8C00BE8B94 /* CoreSearchOptions+Samples.swift in Sources */,
1420F31C29A2800300D4A511 /* CoreSearchResultStub+Samples.swift in Sources */,
F9C557B72670CC5600BE8B94 /* CoreSearchResultStub.swift in Sources */,
Expand Down
3 changes: 2 additions & 1 deletion Tests/Demo.xctestplan
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"testTargets" : [
{
"skippedTests" : [
"MockServerIntegrationTestCase"
"MockServerIntegrationTestCase",
"OfflineIntegrationTests"
],
"target" : {
"containerPath" : "container:MapboxSearch.xcodeproj",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import CoreLocation
@testable import MapboxSearch
import XCTest

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

Expand All @@ -18,7 +18,7 @@ final class AddressAutofillIntegrationTests: MockServerIntegrationTestCase {
)

let engine = LocalhostMockServiceProvider.shared.createEngine(
apiType: CoreSearchEngine.ApiType.autofill,
apiType: Mock.coreApiType,
accessToken: "access-token",
locationProvider: locationProvider
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import CoreLocation
@testable import MapboxSearch
import XCTest

final class CategorySearchEngineIntegrationTests: MockServerIntegrationTestCase {
final class CategorySearchEngineIntegrationTests: MockServerIntegrationTestCase<SBSMockResponse> {
private var searchEngine: CategorySearchEngine!

override func setUp() {
super.setUp()
override func setUpWithError() throws {
try super.setUpWithError()

let apiType = try XCTUnwrap(Mock.coreApiType.toSDKType())
searchEngine = CategorySearchEngine(
accessToken: "access-token",
serviceProvider: LocalhostMockServiceProvider.shared,
apiType: .SBS
apiType: apiType
)
}

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

class DiscoverIntegrationTests: MockServerIntegrationTestCase {
class DiscoverIntegrationTests: MockServerIntegrationTestCase<SBSMockResponse> {
lazy var searchEngine = Discover(locationProvider: DefaultLocationProvider())

func testCategorySearchAlongRouteWithCountryProximityOrigin() throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import CoreLocation
@testable import MapboxSearch
import XCTest

class MockServerIntegrationTestCase: XCTestCase {
let server = MockWebServer()
class MockServerIntegrationTestCase<Mock: MockResponse>: XCTestCase {
typealias Mock = Mock
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: looks like we can remove explicit typealias declaration

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would cause the Test subclasses to need either:

  1. declare a name in the generic, such as <Mock: MockResponse> (more noise, but explicit which is nice) or
  2. change Mock usages to the default name T, such as T.coreApiType.toSDKType() (more obtuse IMHO)

The typealias is inherited and I think it's the simplest approach (although you do have to visit the parent class definition to find the typealias.) Let me know what you think.

let server = MockWebServer<Mock>()

func setServerResponse(_ response: MockResponse, query: String? = nil) throws {
func setServerResponse(_ response: Mock, query: String? = nil) throws {
try server.setResponse(response, query: query)
}

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: MockServerIntegrationTestCase {
class OfflineIntegrationTests: MockServerIntegrationTestCase<GeocodingMockResponse> {
let delegate = SearchEngineDelegateStub()
let searchEngine = SearchEngine()

Expand Down Expand Up @@ -65,7 +65,7 @@ class OfflineIntegrationTests: MockServerIntegrationTestCase {
XCTAssert(region.completedResourceCount > 0)
XCTAssertEqual(region.requiredResourceCount, region.completedResourceCount)
case .failure(let error):
XCTFail("Unable to load Regin, \(error.localizedDescription)")
XCTFail("Unable to load Region, \(error.localizedDescription)")
}
loadDataExpectation.fulfill()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import CoreLocation
@testable import MapboxSearch
import XCTest

final class PlaceAutocompleteIntegrationTests: MockServerIntegrationTestCase {
final class PlaceAutocompleteIntegrationTests: MockServerIntegrationTestCase<SBSMockResponse> {
private var placeAutocomplete: PlaceAutocomplete!

override func setUp() {
Expand All @@ -17,7 +17,7 @@ final class PlaceAutocompleteIntegrationTests: MockServerIntegrationTestCase {
)

let engine = LocalhostMockServiceProvider.shared.createEngine(
apiType: CoreSearchEngine.ApiType.SBS,
apiType: Mock.coreApiType,
accessToken: "access-token",
locationProvider: WrapperLocationProvider(wrapping: DefaultLocationProvider())
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import CoreLocation
@testable import MapboxSearch
import XCTest

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

override func setUp() {
super.setUp()
override func setUpWithError() throws {
try super.setUpWithError()

let apiType = try XCTUnwrap(Mock.coreApiType.toSDKType())
searchEngine = SearchEngine(
accessToken: "access-token",
serviceProvider: LocalhostMockServiceProvider.shared,
locationProvider: DefaultLocationProvider(),
apiType: .geocoding
apiType: apiType
)

searchEngine.delegate = delegate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import CoreLocation
@testable import MapboxSearch
import XCTest

class SearchEngineIntegrationTests: MockServerIntegrationTestCase {
class SearchEngineIntegrationTests: MockServerIntegrationTestCase<SBSMockResponse> {
let delegate = SearchEngineDelegateStub()
var searchEngine: SearchEngine!

override func setUp() {
super.setUp()
override func setUpWithError() throws {
try super.setUpWithError()

let apiType = try XCTUnwrap(Mock.coreApiType.toSDKType())
searchEngine = SearchEngine(
accessToken: "access-token",
serviceProvider: LocalhostMockServiceProvider.shared,
locationProvider: DefaultLocationProvider(),
apiType: .SBS
apiType: apiType
)

searchEngine.delegate = delegate
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@testable import MapboxSearch

extension CoreSearchEngine.ApiType {
/// Available only for tests.
/// This is necessary for tests using subclasses and usages of ``AbstractSearchEngine`` to dynamically instantiate
/// an engine with the appropriate ApiType for their test case mocks.
/// ``AbstractSearchEngine`` and subclasses use a ``MapboxSearch.ApiType`` enum which is a **subset** of the Core
/// ApiType.
/// For all other ApiTypes specialized classes are provided that use the Core ApiType directly (such as
/// AddressAutofill) or support is not yet fully implemented (such as search-box).
/// Tests that attempt to use an Autofill or Search-box API type with a custom SearchEngine (instead of a provided
/// specialized class) should encounter a runtime error.
func toSDKType() -> ApiType? {
switch self {
case .geocoding:
return .geocoding
case .SBS:
return .SBS
case .autofill,
.searchBox:
fallthrough
@unknown default:
fatalError()
}
}
}
2 changes: 2 additions & 0 deletions Tests/MapboxSearchUITests/BaseTestCase.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import XCTest

/// Base XCTestCase class that provides testable application behavior.
/// Please use subclass ``MockServerUITestCase`` for tests.
class BaseTestCase: XCTestCase {
static let defaultTimeout: TimeInterval = 10.0

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@

import XCTest

class CategorySuggestionsIntegrationTestCase: MockServerUITestCase {
class CategorySuggestionsIntegrationTestCase: MockSBSServerUITestCase {
override func setUpWithError() throws {
try super.setUpWithError()
app.launch()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import XCTest

class CategorySuggestionsNavigationIntegrationTestCase: MockServerUITestCase {
class CategorySuggestionsNavigationIntegrationTestCase: MockSBSServerUITestCase {
override func setUpWithError() throws {
try super.setUpWithError()
app.launch()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import XCTest

class FavoritesIntegrationTestCase: MockServerUITestCase {
class FavoritesIntegrationTestCase: MockSBSServerUITestCase {
func testAddRemoveFavorite() throws {
try server.setResponse(.suggestMinsk)
try server.setResponse(.retrieveMinsk)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import XCTest

// TODO: Analytics

class FeedbackIntegrationTestCase: MockServerUITestCase {
class FeedbackIntegrationTestCase: MockSBSServerUITestCase {
override func setUpWithError() throws {
try super.setUpWithError()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import XCTest

class SearchIntegrationTestCase: MockServerUITestCase {
class SearchIntegrationTestCase: MockSBSServerUITestCase {
func testRecentSearchRemove() throws {
try server.setResponse(.suggestSanFrancisco)
try server.setResponse(.retrieveSanFrancisco)
Expand Down
10 changes: 8 additions & 2 deletions Tests/MapboxSearchUITests/MockServerUITestCase.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Foundation

class MockServerUITestCase: BaseTestCase {
let server = MockWebServer()
/// Base UI test case that uses mocked network responses extended on default testable app behavior.
/// Provide a type conforming to MockResponse and then make use of it when using `server` functions and properties.
class MockServerUITestCase<Mock: MockResponse>: BaseTestCase {
aokj4ck marked this conversation as resolved.
Show resolved Hide resolved
let server = MockWebServer<Mock>()

override func setUpWithError() throws {
try super.setUpWithError()
Expand All @@ -17,3 +19,7 @@ class MockServerUITestCase: BaseTestCase {
server.stop()
}
}

/// Specialized default test case that uses SBSMockResponse.
/// SBS is the recommended API engine type and default for the Demo app and UI test cases in the 2.0.0 release series.
typealias MockSBSServerUITestCase = MockServerUITestCase<SBSMockResponse>
Loading