Testing the list of sync-ups
The SyncUpsList feature we have built so far isn’t very complex. In fact, it only has one primary action, that of deleting sync-ups from its collection. However, it can be good practice to test new features as you add them, and in the future we will have much more complicated behavior to test.
Writing your first test
Let’s write a test to verify that when the onDelete
action is sent that the corresponding sync-ups are indeed deleted. This will require us to become familiar with the testing tools in the library, primarily the TestStore
.
The first step to writing a new test is to construct a TestStore
, which is a testable runtime for your feature. It allows you to emulate a series of actions your user takes in your feature, while asserting on how state changes each step of the way, and asserting how effects execute and feed their data back into the system.
As written, this test would fail if we ran it. Go ahead. Try it!
To get a passing test we must provide a trailing closure that describes how the state changes. The closure is handed a single, mutable argument that represents the state before the action was sent. It is your job to mutate that argument to represent the state after the action was processed by the reducer.
That is the basics of writing a test for our feature. This test is quite simple because the logic in the feature is still quite simple. Soon the logic and behavior of the feature will become more complex, and then tests will start to give us more confidence that the feature works as we expect.
Start by creating a new file in your test target called SyncUpsListTests.swift, and paste in some basic scaffolding for a new test to verify the deletion logic.
SyncUpsListTests.swift
TestingListOfSyncUps-01-code-0001.swiftimport ComposableArchitecture import Testing @testable import SyncUps @MainActor struct SyncUpsListTests { @Test func deletion() async { } }
Construct a
TestStore
much like you would a regularStore
, by providing the initial state for the feature to begin in as well as the reducer you want to test. We’ll start the feature with on sync-up already in its array so that we have something to delete.SyncUpsListTests.swift
TestingListOfSyncUps-01-code-0002.swiftimport ComposableArchitecture import Testing @testable import SyncUps @MainActor struct SyncUpsListTests { @Test func deletion() async { let store = TestStore( initialState: SyncUpsList.State( syncUps: [ SyncUp( id: SyncUp.ID(), title: "Point-Free Morning Sync" ) ] ) ) { SyncUpsList() } } }
Send an action into the test store to emulate the user doing something. In this case we are going to send the
onDelete
action to simulate the user swiping on the row and tapping “Delete”. And we can provide anIndexSet
that represents deleting the first row.SyncUpsListTests.swift
TestingListOfSyncUps-01-code-0003.swiftimport ComposableArchitecture import Testing @testable import SyncUps @MainActor struct SyncUpsListTests { @Test func deletion() async { let store = TestStore( initialState: SyncUpsList.State( syncUps: [ SyncUp( id: SyncUp.ID(), title: "Point-Free Morning Sync" ) ] ) ) { SyncUpsList() } await store.send(.onDelete([0])) } }
Provide a trailing closure to
ComposableArchitecture/TestStore/send(_:assert:fileID:file:line:column:)-8f2pl
that mutates the state to its final value. In particular, we expect thesyncUps
to be an empty array, and for no other changes to happen.SyncUpsListTests.swift
TestingListOfSyncUps-01-code-0004.swiftimport ComposableArchitecture import Testing @testable import SyncUps @MainActor struct SyncUpsListTests { @Test func deletion() async { let store = TestStore( initialState: SyncUpsList.State( syncUps: [ SyncUp( id: SyncUp.ID(), title: "Point-Free Morning Sync" ) ] ) ) { SyncUpsList() } await store.send(.onDelete([0])) { $0.syncUps = [] } } }