Unit Testing UIViewController Dismiss Closure in Swift

Hallo alle mensen, Leo Hier. The topic today is unit testing UIViewController dismiss closure in your Swift code.

Today we will attack some tricky unit testing code.

First, let’s talk about testing UIViewController with unit tests. I’m very akin to testing everything. Literally. I think we should have unit tests for everything. Every little piece of code we write should be automated and tested. I know that we all have constraints in our day-to-day life. So testing 100% of things is almost impossible. This way we should choose what battle to fight.

This article attacks one thing that can be a little tricky to test. The dismiss behavior of an UIViewController, and to be fair this is only important if you need to trigger some action after the dismiss or check something. Because if not, you are just testing the UIKit itself.

No worry about all the details, in the article’s end there’s a link to the project on GitHub. The important thing is to study the concepts of dependency injection and unit testing.

Let’s code! But first…

 

Painting of The Day

The painting is a 1598 masterpiece called Saint Catherine of Alexandria from the legendary Painter Michelangelo Merisi da Caravaggio. He was a master Italian painter, father of the Baroque style, who led a tumultuous life that was cut short his by his fighting and brawling.

The history of this painting is fascinating. Saint Catherine of Alexandria was a popular figure in Catholic iconography. She was of noble origins and dedicated herself as a Christian after having a vision. At the age of 18, she confronted the Roman Emperor Maximus (presumably this refers to Galerius Maximianus). Imprisoned by the emperor, she converted into his empress and the leader of his armies. Maximus executed her converts (including the empress) and ordered that Catherine herself be put to death on a spiked wheel. The wheel reportedly shattered the moment Catherine touched it. Maximus then had her beheaded.

I chose this painting because it’s the face we developers do when someone asks on the PR if we plan to add unit tests to new code.

 

The problem – Unit Testing UIViewController Dismiss Closure

You want to unit test that a log service is called when dismissing a UIViewController.

Create a new project called UnitTestDismissTutorial and make sure that the Include Tests option is checked. Clean up the Main storyboard, we will use view code.

Let’s start updating the SceneDelegate.swift file. Replace all your code for the code below:

It will not build but don’t worry.

Now create the HomeViewController.swift file and copy this code to it:

On Mark 1 you can observe what is the most important when we talk about testing code. To be able to be tested the HomeViewController should delegate all of its dependencies to other objects. This example shows that the HomeViewController needs two things two work properly: one object that can log things and one object that provides a DetailsViewController.

To make things interesting the present and the dismiss methods are called separated timers. This implies an extra layer of complexity on our tests, but do not worry we will get there.

We want to test what happens inside the dismiss function closure argument. Is it calling every time? Is it dismissing the view that I think it is?

 

Creating the Protocols

Let’s continue.

Create a LogManager.swift file and copy/paste the code below:

Create a new file called DetailsViewController.swif:

And finally…

Create the file DetailsViewControllerFactory file and copy/paste the code below:

That is the default implementation of the Factory. We need to do that to be able to inject whatever we want inside HomeViewController when unit testing.

 

Unit Testing Dissmis in UIViewController

Now go to your tests and copy-paste the code below:

All Marks Explained

Check below all Mark comments detailed explained:

  1. The dismissExpectation is only necessary because we have to wait until the completion of the dismiss action by the DetailsViewController.
  2. In mark 2 we create our LogManagerSpy, it is a spy because it’s partially implemented. In our case, we just want to make sure it’s called only once by the dismiss.
  3. Now when we create the DetailView ControllerFactoryMock object we need to pass a closure (behavior), we made like this so we could test when all the functions were called and we could fulfill the dismissExpectation.
  4. Now we inject the factory mock and the manager spy to the HomeViewController. Note that in the production code the initialization of HomeViewController is just with no arguments because we are using the default initializers in it.
  5. This is a very important call because it triggers all the UIViewController lifecycle, ending up calling the viewDidLoad function.
  6. Here is how much time we want to wait for the fulfillment of the test. This value can be high because we know the test will finish in less than 0.005 seconds, but if you can’t control the time, it’s time to refactor your code so you can have more control over it.
  7. Mark 7 is where we create the LogManagerSpy, it conforms to the LoggingManager protocol so we can inject it in the HomeViewController, and we add the logRunCount so we can check in the tests how many times we are calling it on our tests.
  8. Mark 8 is the build of the Factory mock. Notice that now we are not returning the DetailsViewController but a mock of it?
  9. Finally, we create a child class of the DetailsViewController. The secret about this implementation is that we can override the dismiss function of it and intercept all his behaviors.

 

Now you just have to press command + U to run the unit test and we finished the exercise today.

 

Summary – Unit Testing UIViewController Dismiss Closure in Swift

Today we observed a lot of unit test techniques. We created a UIViewController that everything is injected using protocols so we can mock in our tests. We also studied how crucial was that to make UIViewController dismissal testable. Additionally, we override the dismiss function of a child implementation to be able to check it in tests. And finally testing asynchronous code with expectations and fulfilments.

That’s all my people, I hope you liked reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or leave a comment saying hello. You can also sponsor posts and I’m open to freelance writing! You can reach me on LinkedIn or Twitter and send me an e-mail through the contact page.

Thanks for reading and… That’s all folks.

Credits:

title image

Share this post:

Related posts

Sponsor