developers in total between Spare, Sbanken and DNB mobile bank. • Monorepo with around ~400 000 lines of Swift. • Mature codebases (mobile bank codebase is around 6 years old, Spare a few more)
that can’t break. • Developers need to be able to con fi dently change code. • It’s important to continuously refactor and improve the codebase. Legacy code is code that you don’t dare to touch. • Automated testing is crucial to maintaining con fi dence in your changes.
new features. • Reproducing edge-cases when doing manual testing. • Generating models and test data in our unit tests. • Ensuring stable, reproducible UI tests. • Demo mode.
returned mock data. • Required a lot of development to simulate the same behaviour as the actual services. • Quickly got out of sync with the actual implementation. • Not really testing our services. protocol AccountsServiceType { func fetchAccounts(completion: ((Result<[Account], E -> } final class AccountsService: AccountsServiceType { ... } final class MockAccountsService: AccountsServiceType { func fetchAccounts(completion: ((Result<[Account], E -> completion?(.success([ Account( ... ), Account( ... ), Account( ... ) ])) } }
to fake our APIs on the HTTP level, instead of creating lots of mocks and stubs. • Uses JSON fi les to make it easy to copy the payload from our test environments. • Allows us to control failures, loading times etc. FakeServer
Each folder is a scenario, where each fi le is an override for the default scenario. • Sub-folders can be used to create sub-scenarios. • Great when developing features, fi xing bugs or doing manual testing.
stable. • Simulating speci fi c scenarios in UI tests allows us to cover very speci fi c edge-cases. • By simulating server-side logic, we can make our UI tests more accurate, without the fl akiness of reaching out to our backend systems.
XCTContext.runActivity(named: "Go to BSU consent page") { _ in let openDetailButton = app.buttons["BSU"].firstMatch openDetailButton.tap() let title = app.staticTexts["Home savings scheme for young people"].firstMatch XCTAssertTrue(title.waitForExistence(timeout: 1)) let openButton = app.buttons["Next"].firstMatch XCTAssertTrue(openButton.waitForExistence(timeout: 1)) XCTAssertTrue(openButton.isEnabled) openButton.tap() } UI testing specific scenarios
to have the test data inline instead of using JSON fi les. • We can setup speci fi c responses for a given endpoint. • Clearer when the test data is right there in the test. func testMessagesSuccess() async { let endpoint = InboxEndpoint.messages(id: "0") fakeServer.registerRoute(FakeRoute( path: endpoint.path, method: endpoint.method.rawValue, response: .jsonObject([ "data": [ [ "senderName": "DNB Bank ASA", "subject": "Interest rate change", "body": "Interest rate has changed", "created": "1970-01-30T10:32:23+0200", "attachments": [] ], [ "senderName": "DNB Bank ASA", "subject": "Interest rate change", "body": "Interest rate has changed", "created": "1970-01-30T10:32:23+0200", "attachments": [] ] ] ]) )) let messages = await inboxService.fetchMessages(id: "0")
writing tests and previews. • By only overriding speci fi c properties, the tests communicate clearly what the actual requirements are for the test to pass.