I have a test where I'm using TaskLocal.withValue() to configure reproducible testing environment.
I end up with code like this:
class MyTest: XCTestCase {
let testEnv = TestEnvironment()
func testZero() {
Environment.$current.withValue(testEnv) {
XCTAssertEqual(sut(value: 0), ...)
}
}
func testPositive() {
Environment.$current.withValue(testEnv) {
XCTAssertEqual(sut(value: +1), ...)
}
}
func testNegative() {
Environment.$current.withValue(testEnv) {
XCTAssertEqual(sut(value: +1), ...)
}
}
...
}
Which is a lot of boilerplate code. In theory, XCTestCase provides methods setUp() and tearDown() which exist exactly as a home for common preparation code before and after test.
But in this case, I cannot use them, because TaskLocal does not provide two separate methods for setup and teardown, for good reasons.
So, I think this should be addressed on the side of the XCTest. There should be a customisation point that allows extracting common setup/teardown code using single method that takes a closure as a parameter. Something like this:
class MyTest: XCTestCase {
override func withEnvironment(_ test: () -> Void) {
let testEnv = TestEnvironment()
Environment.$current.withValue(testEnv) {
super.withEnvironment(test)
}
}
func testZero() {
XCTAssertEqual(sut(value: 0), ...)
}
func testPositive() {
XCTAssertEqual(sut(value: +1), ...)
}
func testNegative() {
XCTAssertEqual(sut(value: +1), ...)
}
...
}
1 Like
mbrandonw
(Brandon Williams)
2
Hi @Nickolas_Pohilets, XCTest actually does have this tool, and it's called invokeTest. You can do:
override func invokeTest() {
let testEnv = TestEnvironment()
Environment.$current.withValue(testEnv) {
super.invokeTest()
}
}
13 Likes
invokeTest was working quite well for me, until I've tried to migrate a subclass of FBSnapshotTestCase:
old code:
override func setUp() {
super.setUp()
let testEnv = TestEnvironment()
Environment.setCurrent(testEnv)
recordMode = true
}
override func tearDown() {
super.tearDown()
Environment.reset()
}
new code:
override func invokeTest() {
let testEnv = TestEnvironment()
Environment.$current.withValue(testEnv) {
// Triggers ObjC exception - "setRecordMode cannot be called before [super setUp]
recordMode = true
super.invokeTest()
}
}
This could be solved by having both invokeTest() and setUp(), but with most of the setup living in the invokeTest(), having setUp() only as a place for recordMode = true looks ugly.
I wonder if there is method to override that wraps invocation of the test without setUp/tearDown?