How to use Mockito with JUnit 5: A Beginner’s Guide

Master unit testing with Mockito and JUnit 5. Learn how to mock dependencies, write efficient tests, and improve Java application quality

Get Started free
How to use Mockito with JUnit 5_ A Beginner’s Guide
Home Guide How to use Mockito with JUnit 5: A Beginner’s Guide

How to use Mockito with JUnit 5: A Beginner’s Guide

Unit testing is one of the first quality checkpoints in software development, and with the right tools, a big difference can be made. Mockito and JUnit 5 are two powerful frameworks that help developers write reliable and maintainable tests.

Mockito allows you to mock dependencies in unit tests, making it easier to test code in isolation, while JUnit 5 is a widely used testing framework for Java.

Combined, they simplify the testing process, especially for complex codebases.

Overview

What is Mockito?

Mockito is a popular Java mocking framework used for unit testing. It allows developers to mock dependencies in their test cases, enabling isolation of the unit under test. With Mockito, you can simulate the behavior of external components, making it easier to test business logic without requiring real implementations.

What is JUnit 5?

JUnit 5 is the latest version of the JUnit testing framework for Java. It introduces a modular architecture, improved annotations, parameterized tests, and better integration with modern Java features like lambda expressions and streams.

Why Integrate Mockito with JUnit 5?

Mockito and JUnit 5 work together to create efficient, isolated unit tests. By integrating Mockito with JUnit 5:

  • Mock dependencies easily test individual components without real implementations.
  • Avoid external dependencies like databases, APIs, or services in unit tests.
  • Leverage JUnit 5 features like extensions to automatically initialize mocks.
  • Improve test reliability by ensuring each test runs in a controlled environment.

This guide walks you through how to integrate Mockito with JUnit 5, breaks down key concepts, and provides hands-on examples to help you get started.

Why use Mockito for Unit Testing?

Unit testing is all about isolating and testing individual units of code. However, many code units depend on external services, databases, or APIs, making them harder to test in isolation. Here’s where Mockito shines.

Importance of Mockito in Unit Testing:

  • Mocking Third-Party Integrations: Mockito allows you to simulate the behavior of external services, ensuring tests focus solely on your code logic.
  • Reduce Test Flakiness: By mocking unpredictable elements, such as network latency or random data, tests become more reliable.
  • Simplify Complex Dependencies: Replace complex objects with simplified mock versions.
  • Verify behavior: Confirm methods are invoked with the right parameters and sequences.
  • Isolate components: Focus on specific units without interference from dependencies.

enhancing unit testing with Mockito

Key Benefits:

  • Flexibility to test any part of your code independently.
  • Clear and concise test logic.
  • Better debugging due to controlled test environments.

What is Mocking?

Mocking is the process of creating a simulated version of a real object. Mocks allow developers to test specific parts of their code by replacing actual dependencies with controlled, predictable versions.

Take a look at a simple example:

java

@Test

void testService() {

    MyService service = Mockito.mock(MyService.class);

    when(service.getData()).thenReturn("Mock Data");




    assertEquals("Mock Data", service.getData());

    verify(service).getData();

}

Mockito vs. Other Mocking Frameworks

Mockito stands out for its simplicity, fluent API, and extensive community support. While frameworks like PowerMock offer advanced features like mocking static methods, Mockito remains a go-to for most use cases.

Here’s a detailed comparison between Mockito and other popular mocking frameworks like PowerMock, EasyMock, and JMock:

FeatureMockitoPowerMockEasyMockJMock
Ease of UseSimple and intuitive API, easy to learnMore complex, requires additional setupModerate, less intuitive than MockitoComplex syntax and learning curve
Mocking Static MethodsNot supportedFully supportedNot supportedNot supported
Mocking Final Classes/MethodsRequires enabling mock-maker-inlineFully supportedLimited supportNot supported
Annotation SupportExtensive (@Mock, @Spy, @InjectMocks)LimitedMinimalMinimal
Integration with JUnitNative integration with JUnit 4 and 5Supported with extensionsSupportedSupported
Code ReadabilityVery clean and readableCan be verbose due to complexityClean, but less expressive than MockitoVerbose and hard to maintain
Community Support and DocumentationLarge community, excellent documentationSmaller community, moderate documentationLimited community supportMinimal community and outdated documentation
PerformanceLightweight and fastRelatively slower due to extended featuresLightweight and fastModerate
Custom VerificationSupported via verify()SupportedSupportedSupported
Mocking ConstructorsNot supportedFully supportedLimited supportNot supported
Mocking Framework ComplexityLowHighModerateHigh
Use CasesGeneral-purpose unit testingAdvanced scenarios needing static/final mockingGeneral-purpose unit testingLegacy projects requiring advanced verification

Core Annotations in Mockito

Mockito provides a set of powerful annotations that help simplify and enhance the process of creating mock objects and testing dependencies.

Here’s a detailed explanation of the most commonly used annotations:

1. @Mock

The @Mock annotation is used to create and inject mock objects into your test class. It helps eliminate the need to manually create mock objects using Mockito.mock().

Key Points:

  • Automatically creates a mock of the specified class or interface.
  • Provides a clean and concise way to define mock objects.
  • Often used for mocking dependencies in unit tests.

When to Use @Mock:

  • To mock external dependencies, such as services, repositories, or APIs.
  • For simulating the behavior of a class or interface without using the actual implementation.

2. @InjectMocks

The @InjectMocks annotation is used to automatically inject mock objects (annotated with @Mock) into the fields of the object under test. This simplifies dependency injection and reduces boilerplate code.

Key Points:

  • Automatically resolves and injects mocks into the target object.
  • Can handle both constructor-based and setter-based dependency injection.
  • Reduces the manual setup required for testing classes with multiple dependencies.

When to Use @InjectMocks:

  • When testing a class that has dependencies injected into it.
  • To reduce boilerplate when setting up complex objects with multiple dependencies.

3. @Spy

The @Spy annotation is used to create partial mocks, which allow real method calls on a mock object. Unlike @Mock, which overrides all methods, @Spy retains the actual behavior of the object but allows mocking specific methods.

Key Points:

  • Partial mocking: calls to real methods are allowed unless explicitly stubbed.
  • Useful for testing real methods while overriding some methods.
  • It can be applied to existing objects or new instances.

When to Use @Spy:

  • To test real methods of a class while controlling specific methods.
  • For classes with a mix of real and mockable behaviors.

Common Mockito Features

Mockito simplifies unit testing with versatile features. Here are some of its core capabilities:

1. Stubbing Methods

Stubbing defines what a mock should return when specific methods are called.

Java

when(mockObject.someMethod()).thenReturn("Stubbed Value");

2. Verifying Interactions

Verification ensures methods are invoked as expected.

Java

verify(mockObject).someMethod();

3. Handling Exceptions

Simulate exceptions for error scenarios.

Java

when(mockObject.someMethod()).thenThrow(new RuntimeException("Error"));

4. Mocking Void Methods

Use doNothing() or doThrow() for void methods.

Java

doNothing().when(mockObject).someVoidMethod();

doThrow(new RuntimeException()).when(mockObject).someVoidMethod();

Core Annotations in JUnit 5

JUnit 5 introduces a powerful set of annotations that help organize and structure your tests effectively. Understanding these annotations and their purpose allows you to write clean, maintainable, and well-structured test cases. Below is a detailed explanation of each core annotation:

1. @Test

The @Test annotation is the most fundamental JUnit 5 annotation. It marks a method as a test case that JUnit should execute.

2. @BeforeEach (Previously @Before)

The @BeforeEach annotation is used to execute a method before each test method in the test class. It is typically used for test setup, such as initializing test data or configuring dependencies.

3. @BeforeAll (Previously @BeforeClass)

The @BeforeAll annotation runs once before all the tests in the test class. It is often used for time-intensive setup operations, such as initializing shared resources.

4. @AfterEach (Previously @After)

The @AfterEach annotation is used to execute a method after each test method. It is typically used for cleanup tasks, such as resetting shared resources or closing connections.

5. @AfterAll (Previously @AfterClass)

The @AfterAll annotation runs once after all the test methods in the test class. It is commonly used for tearing down shared resources.

Have a look at the below example to understand the annotations a bit better.

Java

import org.junit.jupiter.api.*;




public class JunitAnnotationTest {




    @BeforeEach

    void setup() {

        System.out.println("Before each tests.");

    }




    @BeforeAll

    static void globalSetup() {

        System.out.println("Setup before all tests.");

    }




    @AfterEach

    void cleanup() {

        System.out.println("Cleanup after each test.");

    }




    @AfterAll

    static void globalCleanup() {

        System.out.println("Cleanup after all tests.");

    }




    @Test

    void testMethod1() {

        System.out.println("Test 1 executed.");

    }




    @Test

    void testMethod2() {

        System.out.println("Test 2 executed.");

    }

}

Why Integrate Mockito with JUnit 5?

Mockito and JUnit 5 complement each other, enabling:

Using JUnit with Mockito

  • Enhanced Mocking Capabilities: Simplifies testing by seamlessly integrating mocks.
  • Clean Test Design: Reduces boilerplate code.
  • Improved Test Readability: Combines expressive JUnit 5 features with Mockito’s flexibility.

Prerequisites & Setting up the Environment for Mockito with JUnit 5

Here are the prerequisites to set up the test environment for Mockito with JUnit5:

1. Start by adding dependencies to your project

For the app being developed in Maven, Add the following to your pom.xml :

<dependency>

    <groupId>org.mockito</groupId>

    <artifactId>mockito-core</artifactId>

    <version>5.x</version>

</dependency>

<dependency>

    <groupId>org.junit.jupiter</groupId>

    <artifactId>junit-jupiter</artifactId>

    <version>5.x</version>

</dependency>

If Gradle is the preferred build tool for the application, then add it to your build below.gradle :

implementation 'org.mockito:mockito-core:5.x'

implementation 'org.junit.jupiter:junit-jupiter:5.x'

2. IDE Setup

Use IntelliJ IDEA, Eclipse, or your favorite IDE. Ensure the dependencies are downloaded.

Integration Mockito with JUnit 5

Integrating Mockito with JUnit 5 is critical in writing clean, maintainable unit tests for Java applications. Mockito provides several ways to initialize mocks, each tailored to different styles and project requirements.

The most common methods, including their benefits, drawbacks, and practical examples, are explained below.

1. Initializing Mocks Manually

Manually initializing mocks gives you complete control over the mocking process. This method is straightforward and often preferred when you only need one or two mocks in your test.

How it Works:

  • Mocks are created explicitly using Mockito.mock(Class<T>).
  • You have full control over their behavior without relying on annotations.

Example Code:

java

import org.junit.jupiter.api.Test;

import org.mockito.Mockito;




import static org.mockito.Mockito.when;

import static org.junit.jupiter.api.Assertions.assertEquals;




class ManualMockInitializationTest {




    @Test

    void testManualMockInitialization() {

        // Create a mock object

        CalculatorService mockService = Mockito.mock(CalculatorService.class);




        // Define behavior for the mock

        when(mockService.add(5, 10)).thenReturn(15);




        // Call the method under test and verify

        int result = mockService.add(5, 10);

        assertEquals(15, result);

    }




    // A simple service interface for demonstration

    interface CalculatorService {

        int add(int a, int b);

    }

}

When to Use:

This method is ideal for smaller tests or when you need specific, ad-hoc mocks without involving annotations.

2. Annotation-Based Initialization

Annotation-based initialization is one of the most widely used approaches because it keeps the code concise and readable. By using annotations @Mock, you can automate the creation of the mocks.

How it Works:

  • Use @Mock to create mocks.
  • Call MockitoAnnotations.openMocks(this) in a @BeforeEach method to initialize the annotated mocks.
  • MockitoAnnotations.openMocks(this) returns an instance of AutoClosable that you should use to close the mocked service after the tests.

Example Code:

java

import org.junit.jupiter.api.Test;

import org.mockito.Mock;

import org.junit.jupiter.api.BeforeEach;

import org.mockito.MockitoAnnotations;




import static org.mockito.Mockito.when;

import static org.junit.jupiter.api.Assertions.assertEquals;




class AnnotationBasedInitializationTest {




    @Mock

    CalculatorService mockService;

    Calculator calculator;

    AutoCloseable closeable;




    @BeforeEach

    void setup() {

        // Initialize annotated mocks

        Closeable = MockitoAnnotations.openMocks(this);

        calculator = new Calculator(mockService);

    }




    @AfterEach

    void teardown() throws Exception {

        closeable.close();

    }





    @Test

    void testAnnotationBasedInitialization() {

        // Stub the behavior of the mock

        when(mockService.add(10, 20)).thenReturn(30);




        // Verify the result using the real method

        int result = calculator.calculateSum(10, 20);

        assertEquals(30, result);

    }




    static class Calculator {

        private final CalculatorService service;




        Calculator(CalculatorService service) {

            this.service = service;

        }




        int calculateSum(int a, int b) {

            return service.add(a, b);

        }

    }




    interface CalculatorService {

        int add(int a, int b);

    }

}

When to Use:

This approach works best for tests involving multiple mocks and is particularly useful for simplifying test setups in larger classes.

3. Automatic Mock Injection

Mockito also provides the capability to automatically inject mocked services into dependent objects using @InjectMocks.

In the previous example, you manually created an instance of Calculator by injecting a mocked CalculatorService into the constructor of Calculator.

Now, you can use @InjectMocks to automate the same.

How it Works:

  • Call MockitoAnnotations.openMocks(this) in a @BeforeEach method to initialize the annotated mocks. (CalculatorService)
  • Use @InjectMocks to inject mocks into the object under test, which is Calculator in our example.
  • Now you don’t need to write extra code calculator = new Calculator(mockService); to initialize an instance of Calculator.

Example Code:

java

import org.junit.jupiter.api.AfterEach;

import org.junit.jupiter.api.BeforeEach;

import org.junit.jupiter.api.Test;

import org.mockito.InjectMocks;

import org.mockito.Mock;

import org.mockito.MockitoAnnotations;




import static org.mockito.Mockito.when;

import static org.junit.jupiter.api.Assertions.assertEquals;




class AutomaticMockInjectionTest {




    @Mock

    CalculatorService mockService;

    AutoCloseable closeable;




    @InjectMocks

    Calculator calculator;




    @BeforeEach

    void setup() {

        // Initialize annotated mocks

        closeable = MockitoAnnotations.openMocks(this);

    }




    @AfterEach

    void teardown() throws Exception {

        closeable.close();

    }




    @Test

    void testAnnotationBasedInitialization() {

        // Stub the behavior of the mock

        when(mockService.add(10, 20)).thenReturn(30);




        // Verify the result using the real method

        int result = calculator.calculateSum(10, 20);

        assertEquals(30, result);

    }




    static class Calculator {

        private final CalculatorService service;




        Calculator(CalculatorService service) {

            this.service = service;

        }




        int calculateSum(int a, int b) {

            return service.add(a, b);

        }

    }




    interface CalculatorService {

        int add(int a, int b);

    }

}

When to Use:

This is a great choice for reducing boilerplate code, especially when your project uses multiple test classes with complex dependencies.

4. Mockito JUnit 5 Extension

The Mockito JUnit 5 Extension simplifies mock initialization further by leveraging JUnit 5’s extension model. It eliminates the need for MockitoAnnotations.openMocks() by automatically initializing mocks and injecting them into the test.

How it Works:

  • Annotate your test class with @ExtendWith(MockitoExtension.class).
  • Mocks and test objects are initialized and injected seamlessly.

Example Code:

java

import org.junit.jupiter.api.Test;

import org.junit.jupiter.api.extension.ExtendWith;

import org.mockito.InjectMocks;

import org.mockito.Mock;

import org.mockito.junit.jupiter.MockitoExtension;




import static org.mockito.Mockito.when;

import static org.junit.jupiter.api.Assertions.assertEquals;




@ExtendWith(MockitoExtension.class)

class MockitoJUnit5ExtensionTest {




    @Mock

    CalculatorService mockService;




    @InjectMocks

    Calculator calculator;




    @Test

    void testMockitoJUnit5Extension() {

        // Define mock behavior

        when(mockService.add(3, 7)).thenReturn(10);




        // Verify the result

        int result = calculator.calculateSum(3, 7);

        assertEquals(10, result);

    }




    static class Calculator {

        private final CalculatorService service;




        Calculator(CalculatorService service) {

            this.service = service;

        }




        int calculateSum(int a, int b) {

            return service.add(a, b);

        }

    }




    interface CalculatorService {

        int add(int a, int b);

    }

}

When to Use:

This method is perfect when you want to adhere to JUnit 5 best practices and take full advantage of its extension model.

You can find an example code for this guide on Github.

Talk to an Expert

Best Practices for Using Mockito with JUnit 5

Adopting best practices while using Mockito with JUnit 5 ensures that your tests are robust, maintainable, and effective. Below are key recommendations to make the most of these tools:

1. Use Descriptive Test Names

  • Why: Your test name should clearly describe the functionality being tested. For instance, testCalculateSum_withPositiveNumbers_returnsCorrectSum() is more informative than testCalculateSum().
  • Benefit: Descriptive names make it easy for other developers to understand the purpose of the test at a glance.

2. Stub Only What’s Needed

  • Why: Avoid stubbing unnecessary methods on your mocks. For example, if you only need a mock to return a value for one method, don’t stub additional behaviors.
  • Benefit: This keeps tests focused and prevents accidental false positives caused by over-stubbing.

3. Avoid Mixing Real Objects and Mocks

  • Why: Using a combination of real objects and mocks can lead to unpredictable behaviors or hard-to-trace bugs. Always mock dependencies instead of mixing real implementations.
  • Benefit: This isolates the unit under test, ensuring cleaner and more predictable results.

4. Reset Mocks Between Tests

  • Why: If a mock’s behavior is modified in one test, it can unintentionally affect other tests. Use Mockito.reset(mock) or reinitialize mocks before each test.
  • Benefit: This ensures test independence and avoids side effects.

5. Test Edge Cases

  • Why: Always test edge cases such as null inputs, empty collections, or invalid arguments.
  • Benefit: These scenarios often uncover bugs that regular test cases may miss.

6. Verify Interactions

  • Why: Use verify() to ensure that the expected interactions occurred between the unit under test and its dependencies. For instance, verify(mockService).processData(input) confirms the method was called.
  • Benefit: Verification ensures that the unit behaves as expected during execution.

7. Leverage Annotations to Simplify Code

  • Why: Annotations like @Mock, @InjectMocks, and @Spy reduce boilerplate code and improve readability.
  • Benefit: This makes the tests more maintainable and concise.

8. Keep Tests Independent

  • Why: Each test should be self-contained and not rely on the outcome of other tests. Use @BeforeEach or @AfterEach to reset state where necessary.
  • Benefit: This ensures test reliability and prevents cascading failures.

9. Use the Mockito JUnit 5 Extension

  • Why: The extension automatically initializes mocks and reduces manual setup overhead.
  • Benefit: It saves time and aligns with JUnit 5’s modern architecture.

10. Write Tests for Expected and Unexpected Scenarios

  • Why: Test for both positive outcomes and failures, such as throwing exceptions. Use methods like doThrow() to simulate exceptions during mocking.
  • Benefit: This ensures comprehensive test coverage.

BrowserStack Automate Banner

Why choose BrowserStack to execute JUnit and Mockito Tests?

BrowserStack is a cloud testing platform that provides a seamless environment for executing JUnit and Mockito tests on real devices and browsers.

Here’s why you must choose BrowserStack:

1. Access to Real Devices and Browsers: Run tests on 3,500+ real devices and browsers to ensure compatibility across different environments. This helps identify device-specific issues early, reducing post-release defects.

2. Scalability: BrowserStack’s cloud infrastructure can scale to handle large test suites and parallel test execution. Faster test execution means quicker feedback loops, which is essential for CI/CD pipelines.

3. Integration with CI/CD Tools: BrowserStack integrates with popular CI/CD tools like Jenkins, GitHub Actions, and CircleCI. This enables automated test execution as part of your deployment pipeline, ensuring continuous quality.

4. Easy Debugging Tools: BrowserStack provides detailed logs, screenshots, and video recordings of test sessions. Debugging failures is faster and easier, saving precious developer time.

5. Parallel Testing Support: Execute multiple tests simultaneously across different environments. This significantly reduces the time required to run your test suite.

6. Secure and Reliable: BrowserStack is ISO-certified and GDPR-compliant, ensuring your data is secure. Security is crucial, especially for enterprises working with sensitive data.

7. Simplifies Environment Management: You no longer need to maintain a complex local environment with multiple browsers and devices. This reduces operational overhead and simplifies the testing process.

8. BrowserStack Automate for JUnit and Mockito: BrowserStack Automate supports seamless integration with JUnit 5, making it easy to run your Mockito-based unit tests in the cloud. This allows you to validate the functionality of your application under various real-world scenarios.

Refer to this documentation on how to run JUnit 5 tests on Selenium Grid using BrowserStack Automate.

Conclusion

Mockito and JUnit 5 together create a powerful testing duo, enabling developers and testers to write clear, reliable, and maintainable unit tests. Mockito simplifies mocking dependencies, while JUnit 5 provides a modern framework to structure and run tests effectively.

By integrating these tools, you can improve test coverage and ensure your application behaves as expected. Following best practices, such as proper use of annotations and keeping tests clean and focused, further enhances the efficiency of your tests.

Platforms like BrowserStack Automate add significant value for seamless testing across real-world environments.

By choosing BrowserStack, you can elevate the quality of your tests while saving time and effort in setting up and managing local environments. It’s a one-stop solution for developers and QA professionals who want reliable and efficient test execution.

Try BrowserStack Now

Tags
Automation Testing Selenium Website Testing