Every developer knows that testing software can take a lot of time. Being the brilliant industrious individuals that we are, engineers invented multiple ways to test software. Unit testing is just one part among others such as functional testing, integration testing, and acceptance testing.
In this blog, we will go over how to write unit tests for Android using the latest tools and libraries. In this example, I am leveraging JUnit4 and Mockito.
What is Unit Testing?
Tests the smallest functionality of a feature. This is generally a function with a single responsibility. Unit tests shouldn't be concerned with outside dependancies and if they exist in the code, they should be mocked or stubbed. You generally test the public interface of your code which should call on any and all unaccessible implementation hidden within the class.
The goal is to create a test that is relatively simple, easy to debug, fast to execute, and validates that your unit of functionality works within it's own encapsulation before it is used by another object.
Example : Buying a Drink
Scenario
We have a Patron
, Matt, who enters a cafe and buys a cold, refreshing Nuka Cola. To unit test the functionality of buying a drink, we will mock both the Cafe
and DrinkInterface
because the implementation of these objects are largely irrelevant to testing the Patron
class.
Patron (abridged)
public class Patron {
private String mName;
protected float mCash;
protected Cafe mCurrentCafe;
protected ArrayList<DrinkInterface> mDrinksInPossession;
public Patron(String name, float cashAmount) {
mName = name;
setCash(cashAmount);
mDrinksInPossession = new ArrayList<>();
}
public void enterCafe(Cafe cafe) {
cafe.addNewCustomer(this);
mCurrentCafe = cafe;
}
public void purchaseDrinkFromCafe(String drinkName) throws NotEnoughMoneyException {
DrinkInterface drink = mCurrentCafe.getDrink(drinkName);
float cost = drink.getCost();
if (mCash >= cost) {
mCash -= cost;
} else {
return null;
}
DrinkInterface newDrink = mCurrentCafe.sellDrink(drink.getName());
mDrinksInPossession.add(newDrink);
}
}
DrinkInterface
public interface DrinkInterface {
String getName();
float getCost();
float getSizeInOz();
abstract DrinkInterface getCopy();
}
Setup
We create Matt to test the Patron
class.PatronHelper
is a child of the Patron
class which helps reveal unaccesable properties for our tests.
We also create a mocked Cafe
object. It emulates the class without knowing about the implementation.
@Before is called before every test.
@Before
public void SetUp(){
mMatt = new PatronHelper("Matt", 100);
mCafe = mock(Cafe.class);
}
Creating a mocked Drink
DrinkInterface getMockedDrink(String targetDrinkName, float cost) {
//Creates the mocked object based on the class
DrinkInterface nukaColaMock = mock(DrinkInterface.class);
//when getCost is called on the object, return
when(nukaColaMock.getCost()).thenReturn(cost);
when(nukaColaMock.getName()).thenReturn(targetDrinkName);
return nukaColaMock;
}
Creates a mocked or dummy DrinkInterface
object without copying any of the functionality.
DrinkInterface nukaColaMock = mock(DrinkInterface.class);
When getCost is called, it will return the value of cost.
When getName is called, it will return the value of targetDrinkName.
when(nukaColaMock.getCost()).thenReturn(cost);
when(nukaColaMock.getName()).thenReturn(targetDrinkName);
All Together Now
- This creates a Nuka Cola mocked drink.
- Makes sure the mocked cafe returns Nuka Cola.
- Check if Matt has a Nuka Cola soft drink.
@Test
public void BuyDrinkWithEnoughMoney() throws Patron.NotEnoughMoneyException {
//create mocked drink
String targetDrinkName = "Nuka Cola";
DrinkInterface nukaColaMock = getMockedDrink(targetDrinkName, 4);
when(mCafe.sellDrink(targetDrinkName)).thenReturn(nukaColaMock);
mMatt.enterCafe(mCafe);
mMatt.purchaseDrinkFromCafe(targetDrinkName);
assertFalse(mMatt.mDrinksInPossession.isEmpty());
assertTrue(mMatt.mDrinksInPossession.get(0) == nukaColaMock);
}