Skip to main content
Kelvas blog
  1. Posts/

Swift : Async unit tests

·481 words·3 mins
Image

In software development there is one thing that is difficult to achieve : quality.

Anyone can code but few are able to code good software. When I say good I mean robust, stable and malleable according to the necessary evolutions.

But to guarantee such a quality you need tools. Beyond the timeless design pattern, debugging tools or other, there are especially unit tests.

Of course making an iOS application in Swift is no exception to the rule. But as with any language there is always something quite complicated to test, namely asynchronism.

If you don’t have the answer to this question yet and you are a beginner in unit testing or Swift, then I invite you to read the rest of this article.

Like any self-respecting program in iOS, we parallelise a maximum number of tasks. To do this, we need to use DispatchQueues which themselves generate callbacks or completionBlock. This only promises us that we will return to this place in the code at a given moment, but when?

On the other hand, a unit test is much simpler. It is just a function that runs synchronously.

So how to combine synchronous and asynchronous?

If like me you are used to C# nothing could be simpler. You just have to put the keyword async and return a Task type for everything to work. Unfortunately for us and for the moment before Swift 5.5 we have to work with callbacks.

[TestMethod]
public async Task SumTest_WhenInput1And2_Returns3()
{
    var sum = await math.GetSumAsync(1, 2);

    sum.Should().Be(3)
}

In Swift, it will be necessary to wait for the end of the execution of our callback. But how to do this? How to play with time?

We all know a simple method to make code wait for a certain or infinite time until something asynchronous ends: a semaphore or a mutex.

But Apple has already provided for us and everything we need with the Expectation class.

This allows you to define a given time before the mutex is automatically released or the callback arrives.

final class KVFaqResourceLoaderTests: XCTestCase {
    
    func test_load_validResource_Ok() throws {
        
        let expectation = XCTestExpectation(description: "Load local resource")
        let resourceLoader = try KVFaqResourceLoader(
            bundleExplorer: KVFoundationBundleExplorer(),
            bundle: Bundle.module,
            resourceNameWithExtension: "faq.json"
        )
        
        var loadData: Data? = nil
        var loadError: Error? = nil
        
        resourceLoader.load { (data, error) in
            loadData = data
            loadError = error
            expectation.fulfill()
        }
        
        wait(for: [expectation], timeout: 10.0)
        
        XCTAssertNotNil(loadData)
        XCTAssertNil(loadError)
        
    }
}

Nothing very complicated here. It is also possible to use the same Expectation several times for several callbacks.

The time here is important because it prevents your test from ever ending. This way we expect the task to last only a few seconds before it has to stop. In case the expectation stops because of the timeout your test will fail.

I hope you enjoyed this article, don’t hesitate to share it or comment it. See you soon for more articles on mobility.