AndroidPub
Published in

AndroidPub

Basics of Unit Testing

A lot of times, unit testing is discarded because of lack of time, lack of knowledge, or just because the client didn’t ask for it. This article has the goal to show you the importance of unit testing and share some best practices of making it better and easier to start.

What is unit testing?

As the name says, it is testing every area/function (or unit) of your code.

What is the goal of unit testing?

Make sure that those areas/functions (or units) work as expected.

What should I test or what should not I test?

For many people the more common question is which areas/functions to test. In my point of point it is better to define, WHAT I SHOULD NOT TEST:

- Other framework libraries (you should assume they work correctly, because they should have their own tests)

- The database (you should assume it works correctly when it is available)

- Trivial code (like getters and setters )

- Code that has non deterministic results (Think Thread order or random numbers)

WHAT SHOULD I TEST?

- Business logic

- Everything that contains conditions

What are the main advantages of unit testing?

Apart of guarantee that your code is working as expected you also have other advantages, like:

Safe refactoring and better code maintenance

Once you already tested the logic of your code, you can change/improve it, being always aware that the unit tests need to pass.

Most of the developers were a witness of situation like this:

Scenario : “Let’s make a small fix!!!”

Action: Developer changes a “simple” line of code.

Result: This quick fix solve the problem but broke other parts of the code.

  • with unit tests: Unit tests failed -> the developer noticed that -> he refactors the code maintaining the proper logic.
  • without unit tests: The developer noticed the problem in production (worst case scenario).

Fewer bugs

Once you are testing individual pieces of code (consequentiality smaller pieces of code), unit testing helps you to find earlier bugs in your code.

You will always think that your code is working properly, until you make unit tests

Good practices

Name convention

All the tests name should be consistent, therefore it’s a good practice define a name convention for your project. Usually, I’m comfortable to use this one:

test[feature being tested][behaviour expected]

testGetListOfCarsOnSuccessEmptyList

testGetListOfCarsOnSuccessFilledList

testGetListOfCarsOnError

You can see more name convention here.

Positive (and) negative tests

Apart of testing the behaviour expected, we should always be sure that we don’t receive the opposite behaviour.

negative/positive testing

For example, if we are testing a “OnSuccess” callback logic, we should be sure that “OnError” logic is never called. Something like this:

@Test
public void getCarsListOnSuccessEmptyList() throws Exception {

//given
//when
presenter.getCarsList();
//then
carsCaptor.getValue().onNext(new ArrayList<>());
//<POSITIVE TEST>
verify(mockView, times(1)).showCarsList();
//<NEGATIVE TEST>
verify(mockView, never()).showErrorMsg();

verifyNoMoreInteractions(mockView);
}

Template given-when-then

It’s a good habit to “split” a unit test in these 3 blocks (given-when-then)

//given = preparation

  • test initialisation + stubs/mocks creation

//when = execution

  • where the operation to be tested is performed

//then = assertion

  • received result assertion + mocks verification
Given 
a user previously received a gift (that can be open)

When
he opens the gift
Then
he should see the opened gift screen

An alternative test division syntax (with very similar meaning) is AAA = arrange, act, assert.

If you using Java, let’s discover the powerful skills of Mockito. (In case of using Kotlin, have a look at Mockito + Kotlin)

Spy versus Mock

“The mock simply creates a bare-bones shell instance of the Class, entirely instrumented to track interactions with it.” — like a interface

Spy is more than a mock. It wraps an existing instance, where you can “call all the normal methods of the object while still tracking every interaction, just as you would with a mock.”

More info about Spy vs Mock.

InjectMocks

It allows you to create a class that depends of others, without calling the constructor. For example:

You want to test BasePresenter class, that depends of a UseCase.

//base code
public class BasePresenter {

private UseCase useCase;

public BasePresenter(UseCase useCase) {
this.useCase = useCase;
}
public List<Thing> getThings(){
return useCase.getThings();
}
}

generic way

//unit test
public class BasePresenterTest {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Mock UseCase mockUseCase; BasePresenter presenter; @Before
public void setUp {
presenter = new BasePresenter(mockUseCase);
}
@Test
public void testGetThings {
...
presenter.getThings();
...
}
}

mockito with InjectMocks way:

//unit test
public class BasePresenterTest {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Mock UseCase mockUseCase; @InjectMocks BasePresenter presenter; @Test
public void testGetThings {
...
presenter.getThings();
...
}
}

When /ThenReturn

With these instructions you can control and determine whether to return the specified result

List<Thing> controlledList = new ArrayList();
controlledList.add(new Thing("testName1"));
controlledList.add(new Thing("testName2"));
when(presenter.getThings()).thenReturn(controlledList);
//after this, your presenter.getThings will return you a list with 2 "Thing"s

More examples about it here.

Verify number of calls

You can verify how many times your methods are called, the number of interactions with your classes, like this:

// verify is show error msg was called once
verify
(view, times(1)).showErrorMsg(any(ErrorMsg.class));
// verify is show error was never called
verify
(view, never()).showErrorMsg(any(ErrorMsg.class));
//verify if view will not suffer more interactions after this point
verifyNoMoreInteractions(view);
//verify no interactions with all useCase mock verifyZeroInteractions(useCase);//verify order of calls (verify if view.showLoading() is called first of presenter.manageError())InOrder inOrder = inOrder(view, presenter);
inOrder.verify(view).showLoading();
inOrder.verify(presenter).manageError();

Conclusion

“Tests are stories we tell the next generation of programmers on a project.” — Roy Osherove

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store