Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Android App Testing or How To Make Your App Shine

The process of Testing is not easy, especially when it comes to android apps. This often becomes quite harrowing predominantly for beginner developers. From time to time, all Android developers ask these questions: Why should I waste time on writing tests if I only want to write features? (Or the most absurd one) Do I have to write code that tests my code?

But the most important question is what testing means for my business? Why do I need to test my project? Let’s be honest, when running a business, the main goal is to provide the best user experience as possible. This is why you spend an enormous amount of time on testing your apps to make sure everything works properly. Nobody likes when a customer complaints, doesn’t it?

Since apps are focused on specific functionality to provide an improved user experience, apps testing is especially important. Any small bugs will be quickly identified and can influence on the project success (to the worst).

All the love and care you put into your project development, should also be put into your mobile app testing.

We also stick to this rule and take care of every project we have. That’s why we decided to share with you our experience in Android testing and contribute to making your project “shine!”

At first, you might get overwhelmed by all the testing frameworks available for you on Android. In this episode, we go through the process of creating a simple application along with writing tests with some of the most popular testing frameworks; we look at the features they provide us. By reading this article, you can understand why the testing tests are so important, and, get a clearer picture of and how they’re done.

In this project, we use the following frameworks:

  • JUnit
  • Mockito
  • Powermock
  • Robolectric
  • Espresso
  • Hamcrest

Prerequisites

  • Android Studio 3.0
  • Dagger 2 framework
  • RxJava 2
  • Basic understanding of Dependency Injection and Clean Architecture concepts and reactive programming

Why Do I Need To Test Android Apps?

Testing is highly effective because:

  • It provides clients with a bugless, properly working applications.
  • It helps minimize development costs by identifying and correcting bugs before they get into the production.
  • It reduces the app support budget needed for resolving issues in favour of actual feature development.

Testing is good for developers as:

  • It helps getting rid of regression bugs. As a result, there is no need to go over and over the same issues.
  • It ensures that nothing is broken after adding new features and making changes to the code.
  • It makes clear the intentions of what your code is supposed to do.
  • It maintains clean and extensible app architecture.

As you can see, app testing significantly simplifies life for both developers and clients.

Getting dependencies

First, let’s include all the necessary dependencies in our project:

Testing Pyramid(s)

In terms of Android OS, this separation of test types looks valid and informative. However, we prefer to categorize the test types based on whether the tests are executed locally on a development machine or instrumentally on an Android device/emulator.

Android Test Types

Local Unit Testing

This kind of tests are run locally on your development machine. They reside under the folder module/src/test/java and do not have access to any Android framework APIs.

The term «Unit Test» stands for itself. We want to make sure a particular «unit» of our clas works properly, methods return expected values and logic is properly composed. That’s why we need to keep our unit tests small and focused, and targeted at a defined scenario.

Let’s get to some code and see what’s actually going on. We use a number of popular testing frameworks for this unit testing:

  • JUnit. The main framework allows us to write and run Android tests
  • Mockito. You use Mockito to make mocks of your own classes. A mock object is a dummy implementation for an interface or a class where you define the output the certain method calls. Mock objects are configured to perform a certain behavior during the test. Usually, they record the interaction with the system, and the test can validate that. When testing a particular class, you need to use Mockito to mock all of its dependencies and stub their behaviour.
  • Powermock. Using Mockito, you aren’t able to mock everything you want, like static, final or private classes and methods, etc. PowerMock uses a custom classloader and bytecode manipulation to enable mocking of static methods, constructors, final classes and methods, private methods, removal of static initializers and more
  • Robolectric. This is a library containing many mocks of Android classes. The Robolectric test runner injects these “shadow objects” in place of the actual Android classes while the tests are run. This allows running tests on the JVM without booting up an instance of Android. This is especially useful when there is extensive interaction with an Android framework.
  • Local Unit Tests:
  1. Reside under the path module/src/test/java

2. Are fast in execution

3. Run locally on your development machine

Local unit testing:

  1. Verifies the behaviour of classes/methods, and business logic related paraphernalia

2. Is small focused, and targeted at a particular scenario

3. Involves using various mocking techniques

4. JUnit, Mockito, Robolectric, PowerMock

Instrumented Testing

This type of test is executed on a real device or an emulator and needs an Instrumentation information (context, resources, any Android framework dependencies) to run.

This concerns application automated UI testing. For this article, we need only a single framework for Instrumented testing:

  • Espresso. This framework is a part of the Android Support Testing Library along with UiAutomator and JUnit. It facilitates the writing of automated UI tests with the main goal to test how your app’s UI reacts to user interactions.
  • Instrumented Tests:
  1. Reside under the path module/src/androidTest/java

2. Are slow in execution

3. Run on a real device or an emulator

4. Belong to Integration, End-to-End, and User Interface testing categories

Instrumented testing:

  • Integrates between different modules working together
  • Proper UI behaviour
  • Medium/Large in size
  • Espresso, UI Automator, Robotium

When you have some basic understanding of the various types of tests, you can write tests by yourself. However, let’s look at the key concepts to help you get a deeper understanding of how a proper test looks.

Key Concepts

  • Use the Dagger 2 dependency injection framework for providing the necessary dependencies to the classes under test from the outside. However, you shouldn’t create them inside.
  • Try to maintain a clear architecture in your application by the code’s separation of concerns. Nevertheless, do not mix UI code and business logic in one place. Clean Architecture, MVP, MVVM and MVI suit this purpose the best. This way your business logic (Presenters, Use cases, Viewmodels) has no knowledge of (or dependencies on) the Android library. That’s why there’s no need to have mocks of them.

Sample app and testing walkthrough

We include a sample app and do unit and instrumented tests for it. Since this app won’t interact with a network, weare pretending it’s a client-server application.

The user can interact with our app in a variety of ways starting from pressing a Login button or typing his name to storing and retrieving the authentication data from the device’s memory. We need to test various use cases, interactions, and flows.

We have a class responsible for storing and retrieving authentication data in order to authorize a user with a server. Here is how it looks:

Interface

Implementation

Step-by-step:

  • We provide our class with an external SharedPreferences dependency through the constructor:

Pay attention:

If we haven’t passed SharedPreferences into the constructor, we’d do something like this:

public AuthDataLocalStore() {
this.sharedPreferences = new SharedPreferences () {…};
}

However, we wouldn’t be able to mock it and stub it’s behaviour. This is one of the advantages of using DI pattern in your code.

The next step is to define the mock for SharedPreferences in the AuthDataLocalSourceTest. For this purpose, Mockito provides us with its own annotations, @Mock to create mock implementations and @InjectMock, to specify the @Mock fields needing to be possessed.

An alternative to @InjectMocks is to manually create an instance of an object and pass a mocked dependency in every @Test method which you can see in the next example:

  1. First, we need to setup our testing environment. Let’s start with initializing the necessary mocks for the class under the test. We use @Mock annotation to specify which objects we want to mock.

Mockito provides several ways to initialize annotated mocks. This is with the MockitoAnnotations.initMocks(Object testClass) method:

@Before
public void setupStaticMocks() {
MockitoAnnotations.initMocks(this);
}
or by specifying a @Rule for the test class:
@Rule
public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);

Despite the fact the result is the same, the @Mock style is considered to be a bit cleaner.

The method annotated with @Before runs before every @Test method. This is especially useful when several @Test methods need the same objects created before they can run. In our case, we need mocks to be initialized for every @Test method. The opposite is @After annotation run after every @Test method.

Whereis @Rule is the usual way to apply changes to the @Test methods. If you create the same @Before and @After methods across your test classes over and over again, you can also create a single @Rule and include it in your test class. However, we look at the creating a custom @Rule a bit later.

The third option is to manually create mocks inside an @Test method with the Mockito.mock(SomeClass.class) method:

@Test
public void localStore_shouldDoSomething() {
SharedPreferences prefs = Mockito.mock(SharedPreferences.class);
}

2. The next step is to setup Powermock in order to the mock TextUtils.class to contain static methods. However, we can’t do this with Mockito by itself.

@PrepareForTest annotation tells PowerMock to prepare certain classes for testing. Classes, which need to be defined using this annotation, are typically those requiring byte-code manipulation. They are: final classes, classes with final, private, static or native methods that should be mocked and classes that should return a mock object upon instantiation.

Both test classes and individual test methods can be annotated with @PrepareForTest.

In @Before method, we are stubbing the behaviour of the isEmpty() method building our expectation according to what this method should return when called and under what conditions.

Besides, we also need to specify PowerMockRule.

3. Then, we define another testing @Rule:

This is a custom @Rule allowing us to make testing RxJava code more convenient. At first, a testing code with Observables can be tricky. Nevertheless. we tend to use subscribeOn() and observeOn() operators to specify threads which do the work. Usually, we subscribe on a background thread. The Observable stream is executed asynchronously. However, the main challenge with asynchronous execution during the testing is when we subscribe to an Observable. The test itself can finish faster than Observable emits, and the test fails. The solution is to make Observable execute synchronously. We can achieve this by using a Schedulers.trampoline() scheduler. Trampoline schedules work in a FIFO manner. Work is done in the order of subscription synchronously. In order not to do this for every test method, we create a custom rule that extends TestRule. This Rule overrides schedulers being used with trampoline():

4. Now, let’s write some tests.

In AuthDataLocalStore we have a method:

Remember unit tests should be small, focused, and targeted at a specific use case. The getUserEmail() method gets the previously stored user email from the SharedPreferences instance and returns either a Single with email or emits an error if nothing is found. For this particular method, we want to make up some conditions we want to test. For instance:

  • The method should return precisely the email it got from SharedPreferences.
  • The method should call onError if there is no email in SharedPreferences.
  • The method should interact within the SharedPreferences instance.

Now, let’s test all these cases:

For testing purposes RxJava bundles TestObserver class especially when you need to test observable sequences. We have already declared one.

Here’s what this test looks like:

First, we set up our expectation as per what the SharedPreferences mock should do when its methods are called inside getUserEmail() method. Remember, we’ve passed a mock into UserLoginLocalStore, not an actual SharedPreferences, and we need to tell Mockito what to do:

Without this line, all the calls to SharedPreferences mock throw NullPointerException as mocks by themselves do not do anything, and their methods are empty stubs not returning anything.

This basically means:

When the SharedPreference’s getString() method is called with arguments matching any String, then it returns EMAIL string.

Inside Mockito.when(), we pass a method call whose behaviour we are stubbing

anyString(), Mockito ArgumentMatcher. TODO: make description

As you can see, Mockito exposes clean, readable, and fluent api that is very easy to understand.

After this, we subscribe to the Single returned by getUserEmail(). Method test() subscribes to the Single and returns an observer we use for asserting the results.

If the expected conditions are met after Single reaches its terminal event(onSuccess, onError, onComplete), TestObserver exposes a set of methods for asserting.

In this case, we want to assert the subscription that actually took place inside the getUserEmail() method as there were no errors and we received a value taken from the SharedPreferences.

Lets move to the next usecase: getUserEmail() throws an error if SharedPreferenes contains no email:

We’ve done almost the same setup as in the previous test. However, SharedPreferences returns now an empty String we assert as NoSuchElementException is propagated down the observable chain.

Now let’s make a test for the persistEmail() method and verify its behaviour:

We can test the following conditions:

  • An email passed to the method is correctly passed into the SharedPreferences.
  • SharedPreferences is actually used and its methods are called.

Comparing this method with the getUserEmail() method, we can see we don’t read the first one but write it into the SharedPreferences. All the writing transactions are made via SharedPreferences. The Editor interface should also be mocked with its behavior stubbed. After this, we actually call persistEmail() method. The last step is to use Mockito for verifying the correct behaviour of the mock inside the method. Our main goal is to make sure the SharedPreferences.Editor is provided with the correct key for the email value.

argThat() is made precisely for this purpose. Using argThat(), we can verify the argument, which passed into the putString() method, is exactly KEY_EMAIL. We don’t care about the second argument in this test, which we can do with anyString() argument matcher. Remember, the test fails if the key is not the same.

And finally, let’s make a test for the clean() method.

In this test, we can make sure tWell. There’s much to test here. But in the test we can make sure nobody forgets to actually call the clear() method and persist changes with commit() method.

In the last two lines, we verify those methods were called. If someone accidentally (it can happen) removes one of those method calls, the test fails.

Robolectric

Now we are going to do a bit of Robolectric.

Robolectric works by creating a runtime environment including the real Android framework code. If your tests or code from the test call into the Android framework, you’re able to get a much more realistic experience. The code is executed as if it had happened on a real device.

Robolectric can also be used for unit testing, like JUnit + Mockito, when your production code depends on an Android framework. Besides, you can use Robolectric to verify the correct appearance of views in the layout. This is what we’re going to do now.

Imagine our app has a fragment with a layout. We want to make sure all the dimensions, attributes values and text labels strictly conform to the design specification of the app.

Here is a simple layout file we would inspect:

We have an ImageView with Robolectric logo, a TextView with a text, two EditTexts for credentials input and two buttons for logging in and signing up. We are not going to do any networking in the app. However, what we’re going to do is simulate the process of logging in and signing up locally as well as storing user-specific data with SharedPreferences.

The full code of what we test with Robolectric:

Robolectric tests are run with its own runner; we tell JUnit to use it. It’s also required to configure a test class with the app’s BuildConfig:

Before every test method, we need to prepare the fragment for testing, as we are going to access the fragment’s layout and its views:

We need to make sure Textview has the correct title. Here we get the fragment’s view and find a TextView inside with good old findViewById(). After this, we compare the actual text value to the expected one with the JUnit Assert method.

The next step is to find out whether TextView has the required appearance or not:

For working with Android resources, we need a context easily obtained from the started fragment. To verify it the view looks right, we extract actual values from the view and compare them with the expected ones.

Finally we make sure the correct drawable is used with ImageView:

What are Shadows? It’s a set of classes where each Shadow class can modify or extend the behavior of a corresponding class in the Android OS. When an Android class is instantiated, Robolectric looks for a corresponding shadow class. If it finds one, it creates a shadow object to associate with it.

In the example above, we create a Shadow of a Drawable class and acquire its actual resId used in the layout. We also compare it with the drawable we expect to see. This way, you can obtain a Shadow of virtually any Android framework class and access it as if you are accessing a native Android api.

Espresso

Espresso is a testing framework for Android making it easier to write reliable user interface tests.

Espresso is part of the Android Support Repository. Espresso automatically synchronizes your test actions with the user interface of your application. The framework ensures your activity is started before the tests run. It also lets the test wait until all observed background activities are finished.

Important to know: If you want Espresso to work properly, it is recommended animations in your device’s Developer options are turned off. Espresso can not deal with animations correctly.

Let’s get to testing. We start with LoginActivity UI. It has the following layout:

It’s pretty simple:

  • image logo
  • title
  • edit text for authorization/signing up with email
  • button for starting a sign up flow
  • button for starting a login flow

First, we do some basic verification of the layout and it’s behavior:

  • we need to make sure the expected logo and title are displayed on the login screen.
  • we need to make sure the login button gets us to the MainActivity.

Take a look at MainActivityIntentEspressoTest.java:

Here, we have two simple methods for testing the correctness of both the logo and the title: logoTextIsCorrect() and logoIsDisplayed(). But how do they actually work?

The Static Espresso.onView() method expects a ViewMatcher object as an argument. With onView(), we find views on the screen based on the certain criteria. The criterion is expressed with a ViewMatcher. After the view is found, we might want to perform an action on it expressed with a ViewAction object and then check if the view has the expected state with a ViewAssertion.

Take a look at ViewMatchers provided by Espresso:

In this one, our task is to find a view for a specific id:

The ViewMatchers are actually Matcher objects provided by the Hamcrest library that seamlessly integrate with the Espresso framework. Hamcrest is basically a set of Macther we pass into the Espresso methods to do verification of the view matched the Matcher.

Having found the necessary view, we do an assertio according to the one displayed on the screen:

We’ve verified the title text matches our string resource. If not, our test doesn’t work.

Notice while onView() expects a bare Hamcrest Matcher, check() method on the other hand, expects a ViewAssertion. In order to get a ViewAssertion, we need to wrap a Matcher object into a matches() method call. The matches() method returns a ViewAssertion based on the provided Hamcrest Matcher.

As you can see, Espresso’s fluent api allows us to chain method calls and do very laconic view checks. ViewAssertions Espresso provides us with:

And a simple test for checking the image logo and the title are actually displayed on the screen using isDisplayed() Hamcrest Matcher:

These simple tests look quite obvious, but what is this @Rule thing?

As said before, we want to test clicking the login button takes us to the MainActivity provided the user email is correct. In order to do functional testing of Activities, Espresso has ActivityTestRule and IntentTestRule. These rules take care of starting Activities before each test and finishing it after the test ended. IntentTestRule extends ActivityTestRule and provides additional functionality to test Intents for starting other Activities in the context of the Activity under test.

RegisteredRule is a custom rule used to make a certain preparation before testing methods are executed. Since we need to verify the correctness of the Intent constructed on the login button click, we need to extend IntentTestRule. Then we specify what actions need to be done before every @Test method by overriding the beforeActivityLaunched() method. In our case, it would be great to assume the user is already registered and his authorization data is stored in the SharedPreferences, because we should only let the user in if he is already signed up:

This piece of code is executed before every @Test method. IntentTestRule takes care of starting the LoginActivity, lets you use Espresso-Intents api, and on top of that, RegisteredRule handles setting a signed up state.

Take a look at the Espresso-Intents api:

And the test itself:

What we are doing:

  • finding EditText by its id;
  • performing a user email input;
  • closing the keyboard to make the button visible. Otherwise, we are not able to identify the button on the screen as it is covered by the keyboard.
  • Finding the login button by its id and perform a click on it;
  • checking the proper intent is constructed inside the LoginActivity.

Now, we want to test the correct behavior of LoginActivity views for the sign up and login actions. For the sake of reusing code more easily, we have decided to group the tests into 2 separate classes by whether we are testing these actions as registered or unregistered users.

Here are the tests for the Registed state:

As mentioned before, Espresso has a hard time working with animations. We need to replace our animated progress bar with a static drawable. We are actually not interested if the progress bar itself is being animated; we only want to verify the fact it is displayed under certain conditions. These pieces of code replace the drawable used in ProgressBar with a static drawable. You can provide any drawable you have in your resources. This method must be executed before each of the two @Test methods in the class:

Notice we use the RegisteredRule to set up a registered user state.

Unreregistered state:

For this one we define another custom rule. However, this time, we want to set up a situation where a user is not registered. That’s why we clear any data stored in SharedPreferences:

Another big difference in this test class is we have also defined some custom Matchers for the views. Look at these test methods:

What are these hasNoInput(), withProperHint() and withProperError() matchers? For some finer grained view matching, we can provide our own Matcher implementations. For instance, if we need to make sure EditText has no input:

We’ve created an implementation of a Matcher to match an EditText without any input. That’s why, we’re able to verify this by clicking on a login button without any input displaying an error message. Any other custom matchers in the test class are done the same way.

The Bottom Line

Whew! That’s it! We are done with testing this little app for you. If you preserved to the end of this tutorial, you probably understand the main differences between local unit testing and instrumented testing. Additionally, you got an understanding of the basic concepts of how to write unit and UI tests. And, of course, you are acquainted with some popular frameworks. Plus, you know the type of architecture you need to focus on in order to properly test your app and make your code as stable and user-friendly as possible.

The full code you’ll find here.


Android App Testing or How To Make Your App Shine was originally published in JetRuby on Medium, where people are continuing the conversation by highlighting and responding to this story.



This post first appeared on JetRuby Agency - Featured Technical Stories Based In Our Experience, please read the originial post: here

Share the post

Android App Testing or How To Make Your App Shine

×

Subscribe to Jetruby Agency - Featured Technical Stories Based In Our Experience

Get updates delivered right to your inbox!

Thank you for your subscription

×