Photo by Brooke Lark on Unsplash

Testing gestures and actions

Unit testing view's actions in Swift without a host app

Giovani Pereira
Mac O’Clock
Published in
4 min readJun 11, 2021

--

If you love unit tests (either by genuinely liking them or by understanding and fearing the issues that come with the lack of tests) you may always get on a mental deadlock when thinking about testing your views.

What is testing a view? Do snapshot tests count as testing a view? Do I need to change the visibility of my components so I can better test them? We will not be answering these questions here today. But we will be taking a look into how we can unitarily test our view's gestures and buttons to ensure that every action performed will trigger the desired effect.

Let's imagine a simple scenario, you want to test the behavior of the view:

  • If I tap this button, the cell state changes;
  • If I do this gesture, this delegate is called;

Which both makes sense. But the issue here is, how can I simulate these user interactions as tapping a button, or performing a gesture during a unit test?

The easy way

With a host app and a button, it's as simple as a single line of code.

If your button's reference is private, or non-visible for some reason, you may use Reflection to get its reference. Check: Using Mirror to test references in swift.

The method sendActions allows us to send any UIControl.Event to a UIControl — which UIButtons extends from — and trigger your actions.

But… I don't have a Host App

There are many reasons why you may need a host app for your tests, or why you decided to run the tests without a host app (which most likely is about performance, since you don’t need to run an app instance to test every single time).

Performing a selector

How can we substitute the performAction and trigger a button's action? We can get the Selector from the button, and use the performSelector on the view!

And tadaaa! With two weird-looking lines of code, we can achieve the same effect as if we were using a host app and the sendActions.

Simulating a gesture

Sometimes, we may interact with our view using gestures, not buttons, in the form of UIGestureRecognizers. But just like the button, how do we trigger a gesture during a unit test?

This one is a little bit more tricky, but we can access the Gesture Recognizers of the view through the property gestureRecognizers, which gives us an optional array of UIGestureRecognizers.

Then, we can find, inside this array, the gesture we want to perform (considering you may have more than one gesture on the same view). And now is the part it gets weird: We need to get the target from the Gesture Recognizer and extract the Selector String out of it.

With the Selector String in hand, we can now ask our view to perform the Selector and assert the results we are expecting.

Reusing our solution

The previous codes work, but, especially the gesture one, is a little weird to keep repeating every time we want to simulate a user interaction on our tests.

I like to create extensions for the XCTestCase class with both these perform methods, so I can easily reuse them while testing my views.

With these two methods, we can now simplify a lot our test cases:

And use them to test all kinds of button's actions and gestures.

Careful with unknown selectors

This is a nice way to test both:

  • Our buttons and gestures selectors are really working;
  • When performed, they trigger the expected actions;

But, if for some reason you try to perform a selector which doesn't exist, do not fear, it will simply fail your test telling you that you tried to perform an unexistent selector.

Failed test due to unrecognized selector

Which is also a good thing because your button/gesture was not set appropriately and you caught it on the test!

Wrapping up

Maybe I didn't answer the more philosophical questions about views and unit tests, but possibly I gave you a new tool to enhance your tests and unlock a whole new way to look into your views and think about how to test them.

That's all folks! Unit test all the way.

--

--