supposed I have these lines of code:
func reset() {
initializeAnythingElse() {
// AnythingElse
}
initializeHomeData() {
// HomeData
}
}
func initializeHomeData(callback: @escaping (()-> Void)) {
getHomeConfig() {
callback()
}
}
func initializeAnythingElse(callback: @escaping (()-> Void)) {
getAnythingElse() {
callback()
}
}
and I would like to write a unit test for that code. For initializeHomeData
and initializeAnythingElse
, I can write the unit test like :
func testInitializeHomeData() {
let successExpectation = expectation(description: "")
sut.initializeHomeData {
successExpectation.fulfill()
}
waitForExpectations(timeout: 1.0, handler: nil)
// Validation goes here
}
func testInitializeAnythingElse() {
let successExpectation = expectation(description: "")
sut.initializeAnythingElse {
successExpectation.fulfill()
}
waitForExpectations(timeout: 1.0, handler: nil)
// Validation goes here
}
My question is, how to test reset()
? Should I just call them inside testReset()
like :
func testReset() {
testInitializeHomeData()
testInitializeAnythingElse()
}
but I think this is not the proper implementation for that.
Async unit tests that return Task have none of the problems of async unit tests that return void. Async unit tests that return Task enjoy wide support from almost all unit test frameworks.
A synchronous method calls an async method, obtaining a Task . The synchronous method does a blocking wait on the Task .
You are right. To test reset
you need to call reset
, and not it's internal methods.
That being said, reset
is currently written in a way that makes it untestable. The reason you are able to test the other standalone methods so easily is because of the callback
argument both accepts.
I would recommend you rewrite reset to allow two optional callbacks as follows:
typealias Callback = () -> ()
func reset(
homeDataCallback: @escaping Callback? = nil,
anythingElseCallback: @escaping Callback? = nil) {
initializeAnythingElse() {
anythingElseCallback?()
}
initializeHomeData() {
homeDataCallback?()
}
}
Note that this change allows you get notified, in async, when those two internal calls complete.
Now, your test method needs to be written with some sort of synchronization primitive in mind, since logically, reset is only complete when both home data and anything else is done and their callbacks invoked.
There are many ways to achieve this, but I will show you an approach with semaphores:
func testReset() {
let expectation = expectation(description: "reset() completes within some duration")
// some mechanism to synchronize concurrent tasks
// I am using a semaphore
let s = DispatchSemaphore(value: 0)
let homeCallback: Callback = {
s.signal() // signals the completion of home data init
}
let anythingElseCallback: Callback = {
s.signal() // signals the completions of anything else setup
}
// call your reset method as part of the test
reset(homeDataCallback: homeCallback, anythingElseCallback: anythingElseCallback)
// we know we need to wait for two things to complete
// init home data and anything else, so do that
s.wait()
s.wait()
// at this step, reset's internal async methods
// have completed so we can now
// fulfill the expectation
expectation.fulfill()
}
Note that all this change is required to purely allow you to test the reset
call. Your function signature allows you to write reset()
as current in your existing code since it has optional arguments that are both set to nil for default values.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With