Hello everyone,
I’m encountering an issue with my Swift Testing suite where state changes made in one test method do not persist to another. I am using Swift’s @Suite
and @Test
annotations to group and serialize my tests, but it seems that the state is not being carried over between the tests. The second function fails with:
Expectation failed: (string → nil) != nil
Here is my example code:
import Testing
@Suite(.serialized)
struct createCheckTests {
var value = 25
var string: String? = nil
@Test("Create string")
mutating func stringCreation() {
#expect(value > 0)
string = "Value is: \(value)"
}
@Test("Check string")
func stringCheck() {
#expect(string != nil, "The string is nil")
print("\(String(describing: string))")
}
}
What is the correct way to approach such a scenario where I want to test two functions that are related, one to generate some value and one to check that generated value against it initial value using Suites to group and isolate them from other tests?
Thanks.
Hi @TommyGun! Each test function gets its own instance of its suite type (if it's contained in one.) So stringCreation()
and stringCheck()
will operate on separate instances of createCheckTests
. The presence of the .serialized
trait does not affect this functionality.
Having one test function directly rely on the output of another is not something that Swift Testing supports, nor is it something we'd generally recommend as it's very difficult to correctly model inter-test dependencies. Instead, combine the two tests into a single test. For example:
@Suite(.serialized)
struct createCheckTests {
var value = 25
var string: String? = nil
@Test("Create and check string")
mutating func stringCreationAndChecking() {
#expect(value > 0)
string = "Value is: \(value)"
#expect(string != nil, "The string is nil")
print("\(String(describing: string))")
}
}
In the case where multiple tests have similar or identical setup, you can move the setup code up to init()
. If you do that, then your test code can be simplified because you no longer need string
to be optional (in this example):
@Suite(.serialized)
struct createCheckTests {
var value: Int
var string: String
init() {
value = 25
string = "Value is: \(value)"
}
@Test("Check string")
func stringCheck() {
#expect(value > 0)
print(string)
}
}
Now, obviously real-world tests will be more complex than this trivial one, but hopefully I've made it clear how you can leverage init()
to abstract away your creation logic.
With that said, if you really really need shared state, you can use a nonisolated(unsafe) static var
on your suite type instead of an instance property, but keep in mind it's not a recommended pattern and is unsafe to use with parallelized tests. We talk about some of these anti-patterns in the last section of Go further with Swift Testing.