HawSwap x Satoshi Club AMA Recap from 7th of December

Hawswap is a new basic protocol for decentralized financial services based on Ethereum, used to build AMM decentralized exchange HawSwap, trustless auction platform HawLaunch & decentralized option…

Smartphone

独家优惠奖金 100% 高达 1 BTC + 180 免费旋转




Nobody expects Completion Inquisition!

How to test non-called completion handler scenario

Testing completion-handler API happy path scenario is really simple with XCTestExpectation. Things get complicated if your test case has to verify whether given handler was not called. It is still straightforward if it’s called synchronously after some other event that you emulate. But how about a case where execution is deferred in time? In this article, I will demonstrate you a solution to achieve 100% sure that given completion handler will never be called — fast and reliable.

Let’s talk about unit tests of some asynchronous task that provides completion handler — let’s say downloading JSON from a server. It seems obvious that we have to verify if a completion is called in a happy-path scenario. XCTest has nice API for that: just create an expectation, fulfil it in a completion and wait for all fulfillments at the end of a test:

I am pretty sure you saw such kind of tests plenty of times.

However, I don’t see that much test scenarios where we check that something did not happen. One example regards initiating download task that is invalidated/cancelled before it actually finishes (succeeds or fails with an error). At glance, there is nothing wrong with simple XCTFail() in a completion that shouldn’t be called. In case something was wrong (presumably due to a bug), we will have a change to fail current test and see red test in Xcode or CI. But is it really enough? What would happen if our Downloader defines@escaped callback and calls its later e.g. in DispatchQueue.async block?

Just to remind you, unit test always uses main thread to operate and after it reaches the end of a function, it is immediately classified as failed (red) or passed (green). Therefore, deferred execution of XCTFail() does actually log some failure but Xcode is not able to determine which previous test it actually corresponds to. If you are lucky, you will see errors inXcode console, but your entire test will be still green:

Isn’t it misleading? Succeeded test with a failed assertion in a console…

Another approach is to wait a bit and assume that as long completion did not pop up within, let’s say 0.1s, is will never be executed. It has two obvious cons:

Both drawbacks may lead to a dangerous trap:

Let me ask you a question: when do you have 100% sure that some external part of your system will never call a completion? Answer: In a case when no one keeps holds a completion block anymore!

But how to implement it? Of course we cannot define weak reference to a block: ‘weak’ may only be applied to class and class-bound protocol types. So, let’s define simple class ReferenceObserver that just notifies its deinit:

Then, let’s modify a bit our previous test: initalize observer , bind its deallocation handler to fulfilling XCTest expectation (line 4) and capture reference inside our unexpected completion handler (line 8):

As you can expect, let’s get rid of one of helper reference (just callreference = nil) and voilà:

Only completion handler block keeps reference to ReferenceObserver and once completion handler is removed from a memory, deinitCompletion expectation automatically becomes fulfilled:

Here is our final test:

We ended up with a solution that:

You may be wondering, what does 0.1s mean at the end of this test? It is just a maximum interval you let Downloader instance to clear its handler reference.

If you are like me, and you don’t like optionals in your codebase even in your unit tests, you can leverage additional scope to get rid of ReferenceObserver? optional type. Did you know that you can create a do section without any catch, and it creates only an extra scope? If we init our ReferenceObserver in a dedicated scope, its reference will be deleted at the end of it and thus we can remove an optionality. Once //Arrange section ends, the only one reference to an observer will be attached to a downloader. Below notation is equivalent to a previous code:

Sidenote
At the end, let me here remind another golden rule about writing unit tests:

BTW. Such modified test is called a mutation test.

Presented solution suits very well for verifying “not-called” scenario that is fast and reliable. You may also find it useful to check whether something still keeps a reference to a block (with no reason) and potentially introduces memory leak.

Within couple of lines required to implement ReferenceObserver, you are finally sure that a given block will never be called without any delay. So now you can add this technique into you developer toolbox and protect yourself from unexpected Completion Inquisition.

Add a comment

Related posts:

Colored Logs for Python

Logging is an essential part of developing applications and lead to quick debugging and a broader understanding of what’s going on in your app. In this easy and short article we’re going to make our…

The Struggle of Conceptualizing Sexual Assault

A college girl arrives at a man’s house drunk, so drunk that she could feel the ground spinning at a Frat party. She remembers very little of what even happened at the party. She couldn’t even tell…

L2M4 Dumps Basic Tips to Pass Exam

The CIPS L2M4 practice questions present the most beneficial facts. You may drop by one of the most raised results by using L2M4 exam dumps material and achieve each of the Level 2 Certificate in…