How to write unit tests in Clean-Swift

Jeroen de Vrind
Feb 15 · 9 min read
Photo by Helloquence on Unsplash

Structure of a unit test

Different types of test doubles

Add unit test files to your project

Add a new Unit Testing Bundle target

Put your test data in one file

struct Seeds {
struct Movies {
static let slumdogMillionaire = Movie(title: “Slumdog
Millionaire”, releaseDate: “2008–11–12”)
static let hurtLocker = Movie(title: “The Hurt Locker”,
releaseDate: “2009–01–29”)

static let kingsSpeech = Movie(title: “The King’s Speech”,
releaseDate: “noDate”)
static let all = [slumdogMillionaire, hurtLocker,
kingsSpeech]
}
}

Test the Interactor

func presentFetchedMovies(_ movies: [Movie])
func fetchMovies(completionHandler: @escaping ( [Movie]) -> Void)
// MARK: - Test doublesclass PresenterSpy: ListMoviesPresentable {
var presentMoviesCalled = false
var movies: [Movie]?
func presentFetchedMovies(_ movies: [Movie]) {
presentMoviesCalled = true
self.movies = movies
}
}
class MoviesWorkerSpy: MoviesDataProvidable {
var fetchMoviesCalled = false
var movies: [Movie]
init(movies: [Movie] = []) {
self.movies = movies
}
override func fetchMovies(completionHandler: @escaping
([Movie]) -> Void) {
fetchMoviesCalled = true
completionHandler(movies)
}
}
func testFetchMoviesCallsWorkerToFetch() {    // Given
let moviesWorkerSpy = MoviesWorkerSpy()
let sut = ListMoviesInteractor(presenter:
PresenterSpy(), worker: moviesWorkerSpy)
// When
sut.fetchMovies()
// Then
XCTAssert(moviesWorkerSpy.fetchMoviesCalled, "fetchMovies()
should ask the worker to fetch movies")
}
func testFetchMoviesCallsPresenterToFormatMovies() {

// Given
let presenterSpy = PresenterSpy()
let sut = ListMoviesInteractor(presenter:
presenterSpy, worker: MoviesWorkerSpy())
// When
sut.fetchMovies()
// Then
XCTAssert(presenterSpy.presentMoviesCalled,
“fetchMovies() should ask the presenter to format the
movies”)
}
func testFetchMoviesCallsPresenterToFormatFetchedMovies() {

// Given
let movies = Seeds.Movies.all
let presenterSpy = PresenterSpy()
let moviesWorkerSpy = MoviesWorkerSpy(movies: movies)
let sut = ListMoviesInteractor(presenter:
presenterSpy, worker: moviesWorkerSpy)
// When
sut.fetchMovies()
// Then
XCTAssertEqual(presenterSpy.movies?.count,
movies.count, "fetchMovies() should ask the presenter to format
the same amount of movies it fetched")
XCTAssertEqual(presenterSpy.movies, movies,
"fetchMovies() should ask the presenter to format the same
movies it fetched")
}

Test the Presenter

// MARK: - Test doubles
class ViewControllerSpy: ListMoviesDisplayable {
var displayFetchedMoviesCalled = false
var displayedMovies: [ListMoviesViewModel] = []
func displayFetchedMovies(_ movies: [ListMoviesViewModel]) {
displayFetchedMoviesCalled = true
displayedMovies = movies
}
}
// MARK: - Tests
func testDisplayFetchedMoviesCalledByPresenter() {
// Given
let viewControllerSpy = ViewControllerSpy()
let sut = ListMoviesPresenter(viewController: viewControllerSpy)
// When
sut.presentFetchedMovies([])
// Then
XCTAssert(viewControllerSpy.displayFetchedMoviesCalled,
"presentFetchedMovies() should ask the view controller to
display them")
}
func testPresentFetchedMoviesShouldFormatFetchedMoviesForDisplay() {

// Given
let viewControllerSpy = ViewControllerSpy()
let sut = ListMoviesPresenter(viewController: viewControllerSpy)
let movies = Seeds.Movies.slumdogMillionaire
// When
sut.presentFetchedMovies(movies)
// Then
let displayedMovies = viewControllerSpy.displayedMovies

XCTAssertEqual(displayedMovies.count, movies.count,
"presentFetchedMovies() should ask the view controller to
display same amount of movies it receive")

for (index, displayedMovie) in displayedMovies.enumerated()
XCTAssertEqual(displayedMovie.title, "Slumdog Millionaire"
XCTAssertEqual(displayedMovie.year, "2008")
}
}

Test the ViewController

// MARK: — Test lifecyclevar sut: ListMoviesViewController!
var interactorSpy: InteractorSpy!
override func setUp() {
super.setUp()
interactorSpy = InteractorSpy!
sut = ListMoviesViewController(interactor: interactorSpy)
sut.beginAppearanceTransition(true, animated: false)
sut.endAppearanceTransition()
}
func testShouldFetchMoviesWhenViewDidLoad() {

// When
sut.viewDidLoad()

// Then
XCTAssert(interactorSpy.fetchMoviesCalled, "Should
fetch movies when view is loaded")
}
class TableViewSpy: UITableView {
var reloadDataCalled = false
override func reloadData() {
reloadDataCalled = true
}
}
func testShouldDisplayFetchedMovies() {

// Given
let tableViewSpy = TableViewSpy()
sut.tableView = tableViewSpy
let viewModels: [ListMoviesViewModel] = []
// When
sut.displayFetchedMovies(viewModels)
// Then
XCTAssert(tableViewSpy.reloadDataCalled, “Displaying fetched
movies should reload the table view”)
}
func testNumberOfRowsInAnySectionShouldEqualNumberOfMoviesToDisplay() {    // Given
let tableView = sut.tableView
let viewModels: [ListMoviesViewModel] =
[ListMoviesViewModel(title: "Test", year: "1988")]
sut.displayFetchedMovies(viewModels)
// When
let numberOfRows = sut.tableView(tableView!,
numberOfRowsInSection: 1)
// Then
XCTAssertEqual(numberOfRows, viewModels.count, “The number of
tableview rows should equal the number of movies to display”)
}
func testShouldConfigureTableViewCellToDisplayOrder() {    // Given
let tableView = sut.tableView
let viewModels: [ListMoviesViewModel] =
[ListMoviesViewModel(title: “E.T.”, year: “1982”) ]
sut.displayFetchedMovies(viewModels)
// When
let indexPath = IndexPath(row: 0, section: 0)
let cell = sut.tableView(tableView!, cellForRowAt: indexPath)
as! ListMoviesTableViewCell
// Then
XCTAssertEqual(cell.titleLabel?.text, “E.T.”, “A properly
configured table view cell should display the movie title”)
XCTAssertEqual(cell.yearLabel?.text, “1982”, “A properly
configured table view cell should display the movie year”)
}

Running the tests

Turn on Code Coverage in Test Options of build scheme

Short Swift Stories

Articles about Swift that take less than 10 minutes to read.

Jeroen de Vrind

Written by

Short Swift Stories

Articles about Swift that take less than 10 minutes to read.