Dos and Don'ts of Writing Android Unit Tests

Dos and Don'ts of Writing Android Unit Tests

[[183206]]

In this article, I will explain the best practices for writing test cases based on my actual experience. In this article, I will use Espresso coding, but they can be used for unit tests and instrumentation tests. For the above purpose, let's study a news application.

The following content is purely fictional, any similarity is purely coincidental:P

A news app should have the following activities.

  • Language Selection - When the user opens the app for the first time, he has to select at least one language. After selecting, the choice is saved in the shared preferences and the user is redirected to the news list activity.
  • News List - When the user comes to the news list activity, a request with the language parameter is sent to the server, and the response from the server is displayed in the recycler view (with the id of the news list, news_list). If the language parameter is not stored in the shared preferences, or the server does not return a success message, an error dialog will pop up and the recycler view will not be visible. The news list activity has a button that says "Change your Language" if the user only selected one language, or "Change your Languages" if the user selected multiple languages. (I swear to God this is a fictional app)
  • News Details - As the name suggests, this activity will be launched when the user clicks on a news list item.

This app is functional enough, let's delve into the test case written for the news list activity. This is the code I wrote the first time.

  1. /*
  2. Click on the first news item.
  3. It should open NewsDetailActivity
  4. */
  5. @Test
  6. public void testClickOnAnyNewsItem() {
  7. onView(allOf(withId(R.id.news_list), isDisplayed())).perform(RecyclerViewActions
  8. .actionOnItemAtPosition(1, click()));
  9. intended(hasComponent(NewsDetailsActivity.class.getName()));
  10. }
  11. /**
  12. * To test the correct text on the button
  13. */
  14. @Test
  15. public void testChangeLanguageFeature() {
  16. int   count = UserPreferenceUtil.getSelectedLanguagesCount();
  17. if ( count == 1) {
  18. onView(withText( "Choose your Language" )). check (matches(isDisplayed()));
  19. } else if ( count > 1) {
  20. onView(withText( "Choose your Languages" )). check (matches(isDisplayed()));
  21. }
  22. ?}

Think carefully about what to test

In the first test case testClickOnAnyNewsItem(), if the server does not return a success message, the test case will return a failure because the recycler view is not visible. But the purpose of this test case is not like this. Regardless of whether the case is PASS or FAIL, its top requirement is that the recycler view is always visible. If for some reason, the recycler view is not visible, then the test case should not be considered FAILED. The correct test code should look like the following.

  1. /*
  2. Click on   any news item.
  3. It should open NewsDetailActivity
  4. */
  5. @Test
  6. public void testClickOnAnyNewsItem() {
  7. try {
  8. /* To test this case , we need to have recyclerView present. If we don't have the
  9. recyclerview present either due to the presence of error_screen, then we should consider
  10. this test case successful. The test case should be unsuccesful only   when we click on a
  11. news item and it doesn't open NewsDetail activity
  12. */
  13. ViewInteraction viewInteraction = onView(withId(R.id.news_list));
  14. viewInteraction.check (matches(isDisplayed()));
  15. } catch (NoMatchingViewException e) {
  16. return ;
  17. } catch (AssertionFailedError e) {
  18. return ;
  19. }
  20.    //Here we make sure that the news_list recyclerview is visible to the user.
  21.    onView(allOf(withId(R.id.news_list), isDisplayed())).perform(RecyclerViewActions
  22. .actionOnItemAtPosition(1, click()));
  23. intended(hasComponent(NewsDetailsActivity.class.getName()));
  24. }
  25. }

A test case should be complete in itself

When I start testing, I usually test the activities in the following order:

  • Language Selection
  • News List
  • News Details

Because I tested the language selection activity first, before testing the NewsList activity, one of the languages ​​was always selected. But when I tested the News List activity first, the test cases started returning errors. The reason is simple - without selecting a language, the recycler view is not displayed. Note, the order in which the test cases are executed must not affect the test results. Therefore, before running the test case, the language selection must be saved in the shared preferences. In this case, the test case is independent of the test of the language selection activity.

  1. @Rule  
  2. public ActivityTestRule activityTestRule =
  3. new ActivityTestRule(TopicsActivity.class, false , false );
  4. /*
  5. Click on   any news item.
  6. It should open NewsDetailActivity
  7. */
  8. @Test
  9. public void testClickOnAnyNewsItem() {
  10. UserPreferenceUtil.saveUserPrimaryLanguage( "english" );
  11. Intent intent = new Intent();
  12. activityTestRule.launchActivity(intent);
  13. try {
  14. ViewInteraction viewInteraction = onView(withId(R.id.news_list));
  15. viewInteraction.check (matches(isDisplayed()));
  16. } catch (NoMatchingViewException e) {
  17. return ;
  18. } catch (AssertionFailedError e) {
  19. return ;
  20. }
  21. onView(allOf(withId(R.id.news_list), isDisplayed())).perform(RecyclerViewActions
  22. .actionOnItemAtPosition(1, click()));
  23. intended(hasComponent(NewsDetailsActivity.class.getName()));
  24. ?}

Avoid conditional code in test cases

Now in the second test case testChangeLanguageFeature(), we get the number of languages ​​selected by the user, and based on this number, we write if-else conditions to test. But if-else conditions should be written in your code, not in the test code. Each condition should be tested separately. Therefore, in this case, instead of writing only one test case, you need to write two test cases as shown below.

  1. /**
  2. * To test the correct text on the button when   only one language is selected.
  3. */
  4. @Test
  5. public void testChangeLanguageFeatureForSingeLanguage() {
  6. //Other initializations
  7. UserPreferenceUtil.saveSelectedLanguagesCount(1);
  8. Intent intent = new Intent();
  9. activityTestRule.launchActivity(intent);
  10. onView(withText( "Choose your Language" )). check (matches(isDisplayed()));
  11. }
  12. /**
  13. * To test the correct text on the button when more than one language is selected.
  14. */
  15. @Test
  16. public void testChangeLanguageFeatureForMultipleLanguages() {
  17. //Other initializations
  18. UserPreferenceUtil.saveSelectedLanguagesCount(5); //Write anything greater than 1.
  19. Intent intent = new Intent();
  20. activityTestRule.launchActivity(intent);
  21. onView(withText( "Choose your Languages" )). check (matches(isDisplayed()));
  22. }

Test cases should be independent of external factors

In most applications, we interact with external networks or databases. When a test case is run, it can send a request to the server and get a response of success or failure. But it cannot be considered that the test case failed because of the failure information obtained from the server. Think of it this way - if the test case fails, then we modify the client code so that the test case passes. But in this case, do we need to make any changes on the client side? - NO.

But you should not be able to completely avoid testing network requests and responses. Since the server is an external proxy, we can imagine a scenario where it sends some wrong responses that may cause the program to crash. Therefore, the test cases you write should cover all possible responses from the server, even responses that the server will never send. This will cover all the code and ensure that the application can handle all responses without crashing.

Writing test cases correctly is just as important as writing those test codes.

Thanks for reading this article. I hope it helps you write better test cases. You can contact me on LinkedIn. You can also read my other articles here.

For more information, please follow us and you will be notified when we publish new articles.

<<:  Understanding Android security mechanisms

>>:  7 ways to express your love to developers on Valentine's Day

Recommend

Should community O2O bypass property management?

On the one hand, the media is constantly promotin...

Why is my phone charging so slowly? Here's the reason

As mobile phone functions become increasingly pow...

Automobile brand event marketing strategy!

On the evening of April 15, the first online conc...

Beware of new forms of fraud: "AI face-changing" | Digital literacy

Audit expert: Zheng Yuanpan, professor at Zhengzh...

Starbucks' self-harming PR

Starbucks, which has been doing bad things for a ...

There are fewer and fewer people who can write good copy

I must state in advance that this is just my pers...