(String: {%- set hs_blog_post_body -%} {%- set in_blog_post_body = true -%} <span id="hs_cos_wrapper_post_body" class="hs_cos_wrapper hs_cos_wrapper_meta_field hs_cos_wrapper_type_rich_text" style="" data-hs-cos-general-type="meta_field" data-hs-cos-type="rich_text"> <div class="blog-post__lead h2"> <p>While writing tests, it’s important that we aim to test behaviour and not the implementation.&nbsp;</p> </div></span>)

Writing Future Proof Tests in iOS by Adding DSL

Photo of Piotr Szadkowski

Piotr Szadkowski

Updated Jan 5, 2023 • 4 min read
dsl_ios_tests

While writing tests, it’s important that we aim to test behaviour and not the implementation.

This helps us ship faster and prevents people from turning off the tests in the future due to low maintainability.

That’s why it’s worth adding an abstraction layer in the form of DSL (Domain Specific Language).

Saying that, let’s unwrap this phrase for our needs. We’ll do it by taking a look at an example. Think of a game with multiple players, at the end of which we want to present a table view with multiple winners.

Depending on implementation, we might directly have it as a part of the view controller, or have it as a part of a dedicated view. Let’s assume the first scenario:

final class WinnersViewControllerTests: XCTestCase {

func test_onLoad_showsAllWinners() {
// given:
let sut = makeSUT(for: [winnerOne, winnerTwo, winnerThree])

// when:
sut.loadViewIfNeeded()

// then:
XCTAssertEqual(sut.tableView.numberOfRows(inSection: 0), 3)
}

// MARK: Helpers

private func makeSUT(for winners: [Winner] = []) -> WinnersViewController {
return WinnersViewController(winners)
}

private var winnerOne: Winner { Winner(name: "winner 1") }
private var winnerTwo: Winner { Winner(name: "winner 2") }
private var winnerThree: Winner { Winner(name: "winner 3") }
}

The test looks fine. It has nicely separated Given, When, Then sections and covers our WinnersViewController as expected.

At the beginning, we said we wanted to test behavior and not implementation. However, where in the test can we test the implementation?

Take a look at this line:

XCTAssertEqual(sut.tableView.numberOfRows(inSection: 0), 3)

Wouldn’t it be nicer if we could just write:

XCTAssertEqual(sut.winnersDisplayed, 3)

How can we do this?

Simply add an extension at the bottom of the test file.

private extension WinnersViewController {
var winnersDisplayed: Int {
tableView.numberOfRows(inSection: 0)
}
}

If you have the intuition that we should also do something with this magic “0” number, that’s very good! We can get one step further and write inside the extension:

var winnersSection: Int { 0 }

Our example above now looks like:

private extension WinnersViewController {
var winnersDisplayed: Int {
tableView.numberOfRows(inSection: winnersSection)
}
}

Now, this is what we call a domain specific language (DSL). We orient our thinking inside the test not for the current UITableView implementation but rather on the expected business behaviour, which is to display the winners.

What’s more, if in the future we would like to change the collection view display because of new design requirements, we just need to update this small property instead of, for example, checking the table view in all of our tests.

Great work, you just created your own DSL!

Create DSL in iOS tests

If you want to learn more about writing future proof tests in iOSFor further reading we recommend iOS Unit Testing by Example by Jon Reid and Essential Developer

Photo of Piotr Szadkowski

More posts by this author

Piotr Szadkowski

Piotr works as a Senior iOS Developer at Netguru.
Codestories Newsletter  Check what has recently been hot in European Tech.   Subscribe now

We're Netguru!

At Netguru we specialize in designing, building, shipping and scaling beautiful, usable products with blazing-fast efficiency
Let's talk business!

Trusted by: