Android Testing: Local Unit Test

Unit tests in Android has two types.

The Local unit test does not have relate to Android framework, it does not need device or emulator for run the test.

Instrumented unit test is a test that relates to Android framework, it needs device or emulator for run the test.

screenshot-2016-05-27-20-58-11

I use Android Studio 2.1, If you use Android Studio 2.0 or above, you will see the both androidTest and test enabled. But if you use Android Studio version lower than 2.0, you had to choose one of the two types to enable for a run the tests, that mean you had to switch between local unit tests and instrumented unit tests.

In this blog, we will talk about the local unit test. Let’s start!

Setting up your project

Add JUnit dependency in a build.gradle in-app module. Actually, it was added when your project created.

dependencies {
    testCompile 'junit:junit:4.12'
}

And add runner

defaultConfig {
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

Then, sync your Gradle.

Create Math Class

We will start the simple Math class for understanding easier.

public class Math {
    public float sum(float x, float y) {         
        return x + y;     
    }     

    public float minus(float x, float y) {         
        return x - y;     
    }     

    public float multiply(float x, float y) {         
        return x * y;     
    }     

    public float divide(float x, float y) {         
        if (y == 0) {             
            throw new IllegalArgumentException("Divide by zero!");         
        }         
        return x / y;     
    } 
}

Write your first test

Create class MathTest in local unit tests package, we will start with testing sum function.

public class MathTest {

    @Test 
    public void sum() {
        Math math = new Math(); 
        float result = math.sum(3, 2);   
        assertTrue(result == 5); 
    }
}

We test sum method by calling math.sum() and we expect that result of 3 + 2 is 5. If it correct, it will show that the test passed.

test result1

Add the rest of method.

public class MathTest {

    private Math math;

    @Before 
    public void setUp() {    
        math = new Math(); 
    }

    @Test 
    public void sum() { 
        float result = math.sum(3, 2);   
        assertTrue(result == 5); 
    }

    @Test 
    public void minus() {    
        float result = math.minus(-5, 10);    
        assertTrue(result == -15); 
    }

    @Test 
    public void multiply() {    
        float result = math.multiply(10, -20);    
        assertTrue(result == -200); 
    }

    @Test 
    public void divide() {
        float result = math.divide(10, 100);    
        assertTrue(result == 0.1f); 
    }
}

We move initial math object to setUp method. Because we do not want to duplicate it in every test.

Screenshot 2016-05-31 23.55.15

4 tests passed and use time to run it only 2ms. it’s extremely fast!!

@Before annotation will run every time before each test, so math object will be create before sum and minus test running.

@After is opposite of @Before, that mean it will run every time after each test running.

But sometime you want it to run only once time, you should be use @BeforeClass or @AfterClass instead.

And variable in @BeforeClass and @AfterClass must be static.

    private static Math math;

    @BeforeClass 
    public static void setUp() {    
        math = new Math(); 
    }

    @AfterClass 
    public static void tearDown() {     

    }

Catch Exception

Divide method has throw exception when divider is 0. Because it can not find answer, it’s infinity!.

So we can test by add expected exception class.

    @Test(expected = IllegalArgumentException.class) 
    public void divideByZero() {    
        math.divide(10, 0); 
    }

or when you want to catch the exception message. you need to use ExpectedException rule. Adding expect exception class and message, before execute method.

    @Rule
    public ExpectedException exception = ExpectedException.none();

    @Test
    public void divideByZero() {   
        exception.expect(IllegalArgumentException.class);     
        exception.expectMessage("Divide by zero!");    
        math.divide(100, 0);
    }

Parameterized

Sometime you want to test the same method with many tests input. you need parameterized!.

Create new class and add parameterised runner, in this case we need to test sum method with many tests input.

    @RunWith(Parameterized.class) 
    public class MathAddParameterizedTest {

    }

Prepare test input and expected results

    @Parameters
    public static Iterable<Object[]> input() {    
        return Arrays.asList(new Object[][]{             
            {1, 2, 3},             
            {10, -10, 0},             
            {-20, 10, -10},             
            {-10, -33, -43} }); 
        }

Add constructors and variables are relate with testing input and expected results.

Parameterised will run this constructor every time before run the order by test input.

    private float operandOne;
    private float operandTwo;
    private float expectedResult;

    public MathAddParameterizedTest(float operandOne, float operandTwo, float expectedResult) {      
        this.operandOne = operandOne;
        this.operandTwo = operandTwo;    
        this.expectedResult = expectedResult; 
    }

Add test of sum method.

    private Math math; 
 
    @Before 
    public void setUp() {     
        math = new Math(); 
    }

    @Test 
    public void sum() {     
        float result = math.sum(operandOne, operandTwo);     
        assertTrue(result == expectedResult); 
    }

Run it!

test result2

Easy, right? 😀