Unit Testing Custom View in Android

Standard

Preface

In this short article, I want to propose a method on unit testing an Android View.

Anybody who ever wrote UI tests knows it’s quite slow (both in terms of implementation and in terms of execution) and prone to errors and number of problems (especially running on CI). Robolectric improves the situation quite a bit but still has its own problems.

I want to propose an alternative solution, which, while not being applicable everywhere, might provide a faster, better and more stable option for UI testing.

Just give me the code

If you would rather just play with code here’s the repo that contains all the code used in this article. 

High level overview

So what is the approach exactly? 

The main idea is that we don’t need to check if our UI is displayed correctly (most of the time) what we really need is to make sure that we’ve set proper variables or called proper functions with proper arguments.

What does it mean? It means that if we can make sure that our code called:

view.setBackgroundColor(R.color.red)

we don’t need to check that our view became red since we assume that the framework works correctly. And well, if it doesn’t, it is nothing you can fix directly anyway, you can only work around it.

The solution I want to present utilises custom Android view and Mockk, which allows us to achieve exactly what we want – ability to check that methods were called, without dragging the whole Android framework to tests with us. Note that the technique is applicable to Activity, but, since you can’t create an instance of Activity manually, you still will need either Robolectric or instrumentation to provide you with that, which removes much of the benefits of the approach.

Custom view

I will start with the presentation of the code since there is not a lot of it:

And layout looks like:

Note that in init we have only a LayoutInflater call and it’s per design. We will use spyk from Mockk to replace Android functionality with mock, while still maintaining our logic intact. The problem is that for spyk you need an already created instance which means that we cannot do anything except for the basic UI initialization in init. And also this is the reason why we move most of the actual setup code to initialize. It’s one of the drawbacks of the method: you’ll have to split logic and call initialization manually somewhere down the road.

Integrating view into Activity

This is a no-brainer, I add it here for the sake of completeness.

Layout:

Code:

As you can see, it’s just a wrapper to present our CustomView on the actual device with some mock data. 

Testing

Well, finally, we’ve arrived at the meat of the article. Let me present the testing code:

I think it’s better to start from the bottom, where the companion object is. As you can see, we define two functions: to run before the suite and to run after it. In first we make sure LayoutInflater is mocked (so we can mock LayoutInflater.from method later on) and in the second one, we unmock it to make sure other tests are not screwed. In the real scenario, you probably would have those methods in the base abstract class for all View tests.

Next, let’s look at the setUp method at the top of the suite. Here we initialize mocks declared above with @MockK annotation, create a subject for tests with spyk and then set up views for our subject with mocks. This is the second drawback of the method – since there is no “real” layout in tests we need to mock every single UI component we want to verify in tests. That can create quite a bit of boilerplate code, but at the same time allows you to fine-tune code to your needs. 

After this, tests themselves are quite trivial: in the first we verify logic in initialize method, in the second we verify the update method updates UI correctly. 

Closing words

I understand that this approach certainly won’t work for everyone, and it has its drawbacks. However, I do hope it will provide at least some developers with a better way to handle UI testing and maybe will spark a discussion that will result in a much better solution all together!

On this note, I would like to wish you all the best and, hopefully, I will see you in the next article!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s