Unit Testing (JUnit & Mockito)

A unit test checks one small piece of logic — a single method or class — in isolation, running on the plain JVM without a device or emulator. For RecipeBox, that means testing things like "does the Repository correctly filter recipes by category" without ever touching Room, the network, or the Android framework.

Adding the Dependencies

// app/build.gradle
dependencies {
    testImplementation 'junit:junit:4.13.2'
    testImplementation 'org.mockito:mockito-core:5.12.0'
}
Explanation: testImplementation (rather than plain implementation) puts these libraries only on the test classpath — they are never bundled into the shipped RecipeBox APK.

The src/test/java Source Set

Unit test classes live under src/test/java, a source set that Gradle runs directly on the local JVM (via the test Gradle task), which is why these tests are fast — no emulator boot, no APK install.

app/src ├─ main/java/... → app code (RecipeRepository, RecipeViewModel) ├─ test/java/... → unit tests (JVM only, this lesson) └─ androidTest/java/... → instrumented tests (real device/emulator, next lesson)

A First JUnit Test

JUnit4 marks test methods with @Test, runs a method annotated @Before ahead of every test, and checks outcomes with assertEquals and friends.

public class RecipeValidatorTest {

    private RecipeValidator validator;

    @Before
    public void setUp() {
        validator = new RecipeValidator();
    }

    @Test
    public void emptyTitle_isInvalid() {
        Recipe recipe = new Recipe();
        recipe.title = "";

        boolean result = validator.isValid(recipe);

        assertEquals(false, result);
    }

    @Test
    public void validRecipe_isValid() {
        Recipe recipe = new Recipe();
        recipe.title = "Pancakes";
        recipe.prepMinutes = 15;

        assertTrue(validator.isValid(recipe));
    }
}
Explanation:
  • @Before setUp() runs before each @Test method, giving every test a fresh RecipeValidator instance.
  • assertEquals(expected, actual) fails the test (with a clear diff) when the two values don't match.
  • Right-clicking the class (or a single method) in Android Studio and choosing Run executes it directly, showing green/red results in seconds.

Mocking a Repository with Mockito

A RecipeBox ViewModel depends on a RecipeRepository, which in turn depends on Room and the network — dependencies a unit test shouldn't need to actually run. Mockito creates a fake stand-in object that returns whatever the test tells it to.

public class RecipeViewModelTest {

    @Test
    public void loadRecipes_exposesRepositoryData() {
        RecipeRepository mockRepo = mock(RecipeRepository.class);

        List<Recipe> fakeRecipes = Arrays.asList(new Recipe("Pancakes"), new Recipe("Omelette"));
        when(mockRepo.getAllRecipes()).thenReturn(fakeRecipes);

        RecipeViewModel viewModel = new RecipeViewModel(mockRepo);
        List<Recipe> result = viewModel.loadRecipes();

        assertEquals(2, result.size());
        verify(mockRepo).getAllRecipes();
    }
}
Explanation:
  • mock(RecipeRepository.class) creates a fake object satisfying the RecipeRepository type, with no real implementation.
  • when(...).thenReturn(...) stubs a method call: whenever getAllRecipes() is called on the mock, it returns fakeRecipes instead of hitting Room.
  • verify(mockRepo).getAllRecipes() confirms the ViewModel actually called that method — not just that the final result happened to look right.

What NOT to Unit Test

Plain JUnit unit tests run on the local JVM, which has no real Android framework classes — Context, View, Activity, and Room's actual database engine either don't exist or throw "not mocked" errors when touched directly from src/test/java.

Code that genuinely needs those classes belongs in Robolectric tests (Android framework simulated on the JVM) or Espresso instrumented tests (a real device or emulator) — both covered in the next two lessons.

Unit Test vs Instrumented Test

Unit test (this lesson)Instrumented test
Source setsrc/test/javasrc/androidTest/java
Runs onLocal JVMReal device / emulator
SpeedFast (no APK install)Slower (installs and launches the app)
Android framework classesNot availableFully available
Good forValidators, Repositories, ViewModels (with mocks)UI interactions, real Room queries, full app flows
Unit tests confirm the logic is correct in isolation. The next lesson, UI Testing (Espresso), checks that RecipeBox's actual screens respond correctly when a real user taps and types on them.