Blog
navigate_next
Java
Java mocks: A guide to mocking in Java
Shardul Lavekar
January 17, 2024

Testing is an integral part of the software development life cycle. Unit testing specifically is a form of testing in which the developer writes code to test parts of the codebase. Testing helps us developers write better code.

What is mocking?

Mocking is part of unit testing an Object-Oriented project. Mocking is the practice of creating simulated versions of the objects in the code, and then using these mock objects to mimic the behavior of the real objects or services so that we can test parts of the code in isolation.

We use mocks to control the inputs to certain methods on our objects. We can then stub the output (define predetermined behavior) and observe the results of tests. For example, we can set a predetermined response from an API endpoint to run tests without actually making the connection or sending the request. Another example would be to mock data persistence methods to be able to run tests without making a connection to the database.

Mocking is a testing methodology used in test-driven development (TDD).

How to create mocks in Java

Let's look at a simple example of a mock in Java. Consider the following class:


public class MiniCalculator {
    public int add(int a, int b) {
        return a + b;
    }
}

Here we create a class called <span class="pink">MiniCalculator</span> and a method called <span class="pink">add()</span> that sums two integers passed to it and returns the result.

Now we'll create a class that imports and uses the <span class="pink">MiniCalculator</span> class:


public class SampleApplication {
    MiniCalculator miniCalculator;

    public SampleApplication(MiniCalculator miniCalculator) {
        this.miniCalculator = miniCalculator;
    }

    public int performAddition(int a, int b) {
        return miniCalculator.add(a, b);
    }
}

In a unit test, we can create a mock of the <span class="pink">MiniCalculator</span> class and set some behavior to it:


@Test
public void runSimpleExampleTest() {
    // Create a mock instance of the MiniCalculator class
    MiniCalculator calculatorMock = mock(MiniCalculator.class);

    // Set up stubbing for behavior of the mock's add() method
    when(calculatorMock.add(3, 4)).thenReturn(7);

    // Create an instance of SampleApplication that depends on MiniCalculator
    SampleApplication sampleApplication = new SampleApplication(calculatorMock);

    // Perform the operation that relies on the calculator
    int addResult = sampleApplication.performAddition(3, 4);

    // Verify that there was some interaction with our mock
    verify(calculatorMock, times(1)).add(3, 4);

    //Check that the result is correct
    assert addResult == 7;
}

Let's take a look at what's happening here.

  • First, we set up a mock object and create a stub by setting a predetermined outcome for the <span class="pink">add()</span> method:

MiniCalculator calculatorMock = mock(MiniCalculator.class);
when(calculatorMock.add(3, 4)).thenReturn(7);

  • We then create an instance of the <span class="pink">SampleApplication</span> class and pass the created mock through the constructor. The <span class="pink">performAddition()</span> method calls the <span class="pink">add()</span> method on the mock:

SampleApplication sampleApplication = new SampleApplication(calculatorMock);

int addResult = sampleApplication.performAddition(3, 4);

  • Finally, we verify that the <span class="pink">add()</span> method was executed on the mock and assert that the value returned in the sample application's <span class="pink">performAddition()</span> method is 7, as we expected.

Simplify unit testing with the Unlogged IDE plugin, a powerful tool that revolutionizes automated regression testing for your Java projects. With Unlogged, you can generate JUnit tests on the fly.

A demo of using Unlogged to simplify unit testing, in this gif, using the Direct invoke feature

Java mocking with Mockito

A dedicated mocking library like Mockito can help you effectively create mock objects to test your code's functionality in isolation. While you can mock without a library in Java, using one is usually more efficient, especially for larger or more complex projects.

Let's set up a sample Java project to explore Mockito's functionality.

Prerequisites

You'll need to have the following installed and configured to follow along:

Creating a sample Java project

We will use IntelliJ IDEA to create the project for our test application.

In IntelliJ IDEA, select File → New Project.

Creating a sample Java project step 1

Choose Java as the language and Maven as the build system. Deselect the Add sample code checkbox. Click Create.

Creating a sample Java project step 2

You should now have an empty project with the following folder structure:

Creating a sample Java project step 3

Now create a source package for the sample application. Right-click the src → main → java folder, select New → Package and set the package as <span class="pink">org.sample.application</span>.

Creating a sample Java project step 4

We also need to create a package that will contain our unit tests. Right-click the src → test → java folder, select New → Package, and set the package as <span class="pink">org.sample.test</span>.

Creating a sample Java project step 5

Next, create a class to house our unit tests with the mocks. Right-click the newly created <span class="pink">org.sample.test </span>package and select New → Java Class. Name the class <span class="pink">SampleTests</span>.

Creating a sample Java project step 6

Your new project structure should look as follows:

Creating a sample Java project step 7 - Getting the right project structure

Setting up Mockito

In addition to Mockito, we'll use the testing framework JUnit, Maven Surefire to run the tests during compile time, and Lombok to reduce boilerplate code. You can use Lombok annotations in code to automatically generate common methods like getters and setters during compilation, reducing the amount of code you need to write for basic data accessors.

To set up these dependencies, we need to add them to our <span class="pink">pom.xml</span> file. POM stands for "Project Object Model", and the file contains all the configuration information for a project.

Add the following to the <span class="pink">pom.xml</span> file after the closing <span class="pink"></properties></span> tag:


 <dependencies>
        <!-- JUnit dependency, please see below url for versions -->
        <!-- <https://mvnrepository.com/artifact/junit/junit> -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

        <!-- Mockito dependency, please see below url for versions -->
        <!-- <https://mvnrepository.com/artifact/org.mockito/mockito-core> -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>5.8.0</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20231013</version>
        </dependency>

         <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <scope>provided</scope>
         </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- Other plugins -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.2</version> <!-- Use the latest version available -->
            </plugin>
        </plugins>
    </build>

Now we need to download the dependencies to our local maven repo. Open the terminal or command prompt and change the directory to make sure you're in the project root folder <span class="pink">MockinInJava</span>.

Run the following Maven command to download the dependencies:


mvn dependency:resolve

You should see a result similar to the following:


[INFO]
[INFO] The following files have been resolved:
[INFO]    org.junit.jupiter:junit-jupiter-api:jar:5.10.1:test
[INFO]    org.opentest4j:opentest4j:jar:1.3.0:test
[INFO]    net.bytebuddy:byte-buddy:jar:1.14.10:test
[INFO]    org.mockito:mockito-core:jar:5.8.0:test
[INFO]    org.apiguardian:apiguardian-api:jar:1.1.2:test
[INFO]    org.junit.platform:junit-platform-commons:jar:1.10.1:test
[INFO]    net.bytebuddy:byte-buddy-agent:jar:1.14.10:test
[INFO]    org.objenesis:objenesis:jar:3.3:test
[INFO]    org.projectlombok:lombok:jar:1.18.30:provided
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.303 s
[INFO] Finished at: 2023-12-09T11:55:35+02:00
[INFO] ------------------------------------------------------------------------

Once the dependencies have been downloaded and resolved, we need to sync the project in the IDE.

Reload the project using the Maven plugin. Click the Maven icon on the right side of the IntelliJ IDEA window (1), then select the refresh icon (2):

Reloading the project using the maven plugin

Now you should see the added dependencies under External Libraries in our project structure.

All the added dependencies under External Libraries visible in the project structure

Unit testing

Let's add a unit test to our project where we can set up some mocks. In the <span class="pink">SampleTests.java</span> class file, delete the current content and add the following:


package org.sample.test;

import org.junit.Test;

public class SampleTests {

    @Test
    public void runTest() {
        System.out.println("Running unit test for mocking");
    }

}

A couple of points to note:

  • By default, the Maven Surefire plugin will detect and run unit test functions in class files located in the test folder. For Surefire to detect a unit tests class file, the file must adhere to the following naming conventions:
  • <span class="pink"> \*\*/Test\*.java </span>
  • <span class="pink"> \*\*/\*Test.java </span>
  • <span class="pink"> \*\*/\*Tests.java </span>
  • <span class="pink"> \*\*/\*TestCase.java </span>
  • Add the <span class="pink">@Test</span> annotation (imported from <span class="pink">org.junit.Test</span>) to mark a method as a unit test.

We can now run our test file to make sure that our unit test executes as expected. In the IntelliJ IDE, select Run → Run 'SampleTests.java'.

Running the test file to make sure that the unit test executes as expected.

If all is well, the simple unit test will run successfully.

Result: unit test will successfully.

Java mocking with the Unlogged IDE plugin

Let's also use the Unlogged IDE plugin to see how it makes unit testing easier. With the Unlogged plugin, you can:

  • Generate JUnit tests on the fly.
  • Inject mocks into code during runtime and replay the methods with mocks enabled.
  • Use direct invocation to test methods with specified arguments.

To set up the plugin in the IntelliJ IDEA, go to File → Settings or IntelliJ IDEA → Settings on macOS.

Installing the Unlogged IDE plugin step 1

Select Plugins, and search for "Unlogged".

Installing the Unlogged IDE plugin step 2 - Jetbrains marketplace

Click Install. Once installed, the plugin will show an "Installed" status.

Installing the Unlogged IDE plugin step 3

We need to add some new dependencies and a plugin item to our <span class="pink">pom.xml</span> file.

Add these dependencies in the <span class="pink"><dependencies></span> tag:


<dependency>
  <artifactId>unlogged-sdk</artifactId>
  <groupId>video.bug</groupId>
  <version>0.1.37</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.16.0</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.16.0</version>
</dependency>

Add the following plugin in the <span class="pink"><plugins></span> tag:


<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <annotationProcessorPath>
                <artifactId>unlogged-sdk</artifactId>
                <groupId>video.bug</groupId>
                <version>0.1.37</version>
            </annotationProcessorPath>
            <annotationProcessorPath>
                 <groupId>org.projectlombok</groupId>
                 <artifactId>lombok</artifactId>
                 <version>1.18.30</version>
            </annotationProcessorPath>
        </annotationProcessorPaths>
    </configuration>
</plugin>

Run the Maven dependency resolve command to download the Unlogged dependencies and plugin:


mvn dependency:resolve

You should see the <span class="pink">BUILD SUCCESS</span> result:

Installing the Unlogged IDE plugin step 4 - build success result

Finally, reload the project using the Maven plugin. Click the Maven icon on the right side of the IntelliJ IDEA window, then select the refresh icon.

The Unlogged plugin is now ready to use.

Sample help-desk application

Now we can set up a sample application and implement some standard mocking.

We'll create a help-desk application for a wind turbine company that customers can use to log incidents of faulty equipment or request a service.

The application is created with mocking in mind, and the incidents are managed in memory using a list object. To interact with the application outside of unit testing would require the implementation of a REST API.

Create the following Java class files under the <span class="pink">org.sample.application</span> package.

Create a <span class="pink">HelpdeskApplication.java</span> file and add the following code:


package org.sample.application;

import java.io.InputStream;
import java.util.Properties;
import java.util.logging.Logger;

public class HelpdeskApplication {

    private static final String CONFIG_FILE = "/config.properties";
    private static final Logger logger = Logger.getLogger(HelpdeskApplication.class.getName());

    public static void main(String args[]) {
        MonitorThread thread = new MonitorThread();
        thread.startThread();
    }

    public static String getApiKey() {
        Properties properties = new Properties();

        try (InputStream input = HelpdeskApplication.class.getResourceAsStream(CONFIG_FILE)) {
            if (input == null) {
                logger.warning("Unable to find " + CONFIG_FILE);
                return "";
            }

            properties.load(input);
            return properties.getProperty("api.key");

        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

}

Create a <span class="pink">MonitorThread.java</span> file and add the following code:


package org.sample.application;

import java.util.logging.Logger;

public class MonitorThread extends Thread {
    boolean running = false;
    private static final Logger logger = Logger.getLogger(MonitorThread.class.getName());

    public void startThread() {
        running = true;
        start();
    }

    public void stopThread() {
        running = false;
    }

    public void run() {
        while (true) {
            try {
                logger.info("Incident list monitor, current list size = " + IncidentManager.incidents.size());
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Create an <span class="pink">Incident.java</span> file and add the following code:


package org.sample.application;

import java.util.Date;

import lombok.Data;
import lombok.AllArgsConstructor;

@Data
@AllArgsConstructor
public class Incident {
    private String guid;
    private Date dateTimeCreated;
    private String customerAccountNo;
    private String title;
    private String description;
    private String feedback;
    private double latitude;
    private double longitude;
    private Weather weather;
}

Create a <span class="pink">Weather.java</span> file and add the following code:


package org.sample.application;

import lombok.Data;

@Data
public class Weather {
    private double temp;
    private double windSpeed;
}

Create a <span class="pink">WeatherProvider.java</span> file and add the following code:


package org.sample.application;

import org.json.JSONObject;

public interface WeatherProvider {
    public Weather getLocationWeather(double lat, double lon);
}

Create an <span class="pink">OpenWeatherProvider.java</span> file and add the following code:


package org.sample.application;

import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.logging.Logger;


public class OpenWeatherProvider implements WeatherProvider {

    private static final Logger logger = Logger.getLogger(OpenWeatherProvider.class.getName());
    public Weather getLocationWeather(double lat, double lon) {
        Weather currentWeather = new Weather();
        try {
            String api_key = HelpdeskApplication.getApiKey();
            String url = "";
            url = url.replace("@lat", String.valueOf(lat));
            url = url.replace("@lon", String.valueOf(lon));
            url = url.replace("@api_key", api_key);

            JSONObject jsonObject = callAPI(url);

            if (jsonObject != null) {
                currentWeather.setTemp(jsonObject.getJSONObject("current").getDouble("temp"));
                currentWeather.setWindSpeed(jsonObject.getJSONObject("current").getDouble("wind_speed"));
            }

        } catch (Exception e) {
            logger.severe("Exception occurred in getLocationTemperature() - " + e.getMessage());
        }

        return currentWeather;
    }

    private JSONObject callAPI(String url) {
        try {
            HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
            connection.setRequestMethod("GET");
            JSONObject jsonObject = null;
            // Check if the request was successful (HTTP status code 200)
            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                // Read the response into a StringBuilder
                StringBuilder responseStringBuilder = new StringBuilder();
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        responseStringBuilder.append(line);
                    }
                }
                // Parse the response string into a JSONObject
                String jsonResponse = responseStringBuilder.toString();
                jsonObject = new JSONObject(jsonResponse);
                return jsonObject;
            }
        } catch (Exception e) {
            logger.severe("Exception occurred in callAPI() - " + e.getMessage());
        }
        return null;
    }

}

Create an <span class="pink">IncidentManager.java</span> file and add the following code:


package org.sample.application;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.UUID;
import java.util.logging.Logger;

public class IncidentManager {

    private static final Logger logger = Logger.getLogger(IncidentManager.class.getName());
    public static ArrayList incidents = new ArrayList();

    private WeatherProvider weatherProvider;

    public ArrayList findAllIncidents() {
        return incidents;
    }

    private void setWeatherProviderIfNull() {
        if (weatherProvider == null) {
            weatherProvider = new OpenWeatherProvider();
        }
    }

    public Incident findIncident(String guid) {
        setWeatherProviderIfNull();
        Iterator it = incidents.iterator();
        while (it.hasNext()) {
            Incident incident = it.next();
            if (incident.getGuid().equals(guid)) {
                return incident;
            }
        }

        return null;
    }

    public Incident findIncidentByTitle(String title) {
        setWeatherProviderIfNull();
        Iterator it = incidents.iterator();
        while (it.hasNext()) {
            Incident incident = it.next();
            if (incident.getTitle().equals(title)) {
                return incident;
            }
        }

        return null;
    }

    public String createIncident(Incident incident) {
        setWeatherProviderIfNull();
        // Generate a unique id and set to the incident before creating it
        String guid = UUID.randomUUID().toString();
        incident.setGuid(guid);

        // Let's get the current weather
        try {
            Weather currentWeather = weatherProvider.getLocationWeather(incident.getLatitude(), incident.getLongitude());
            incident.setWeather(currentWeather);

            incidents.add(incident);
        } catch (NullPointerException ne) {
            logger.severe("Null pointer exception thrown in createIncident()");
            guid = "";
        }
        // Returns the id of the incident
        return guid;
    }

    public boolean deleteIncident(String guid) {

        setWeatherProviderIfNull();

        Iterator it = incidents.iterator();
        while (it.hasNext()) {
            Incident incident = it.next();
            if (incident.getGuid().equals(guid)) {
                incidents.remove(incident);
                return true;
            }
        }

        return false;
    }

    public boolean modifyIncident(Incident incident) {

        setWeatherProviderIfNull();

        Iterator it = incidents.iterator();
        int pos = 0;
        while (it.hasNext()) {
            Incident checkIncident = it.next();
            if (checkIncident.getGuid().equals(incident.getGuid())) {
                incidents.set(pos, incident);
                return true;
            }
            pos++;
        }

        return false;
    }

    public void setWeatherProvider(WeatherProvider weatherManager) {
        this.weatherProvider = weatherManager;
    }
}

Let's take a look at what each of these class files is used for:

  • <span class="pink">HelpdeskApplication</span> is our main application entry point and takes care of creating the monitor thread and providing the API key we will need later.
  • <span class="pink">MonitorThread</span> reports the number of incidents on the incident list every two seconds.
  • <span class="pink">Incident</span> is the object representing incidents to be logged. We'll execute the typical CRUD actions on this object.
  • <span class="pink">Weather</span> is an object in the <span class="pink">Incident</span> class that we'll use to retrieve the current temperature and wind speed from a weather API and log this information when an incident is logged.
  • <span class="pink">WeatherProvider</span> is an interface that allows us to implement multiple weather API methods.
  • <span class="pink">OpenWeatherProvider</span> is an implementation class of the <span class="pink">WeatherProvider</span> interface that integrates with the OpenWeather API.
  • <span class="pink">IncidentManager</span> takes care of all the CRUD actions on our list of incidents.

We should by now have a project structure similar to this:

Expected project structure for reference during project setup

How to create mock objects in Java

Now we'll add mock instances of <span class="pink">WeatherProvider</span> and <span class="pink">IncidentManager</span> to the unit test file.

Open the <span class="pink">SampleTests.java</span> file and add the required imports below the <span class="pink">package</span> statement:


import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.sample.application.*;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.Mockito.*;
import java.util.Date;

Add the following in the <span class="pink">SampleTests</span> class declaration:


@Mock
private WeatherProvider weatherProvider;

@InjectMocks
private IncidentManager incidentManager;

@Before
public void setup() {
  MockitoAnnotations.initMocks(this);
}

In the code above, we use Mockito annotations to create the mock objects that will be initialized before each of our unit tests.

How to mock an interface in Java

Next we'll test the <span class="pink">createIncident()</span> method by injecting a mock of the <span class="pink">WeatherProvider</span> interface into the <span class="pink">IncidentManager</span>. We then create stubs so that our mock <span class="pink">weatherProvider</span> returns a certain <span class="pink">Weather</span> object when called. In effect, we are mocking the REST API.

Create the <span class="pink">testCreateIncident()</span> unit test by adding the following code just below the <span class="pink">setup()</span> method we added in the previous step:


@Test
public void testCreateIncident() {
    incidentManager.setWeatherProvider(weatherProvider);

    // Stubbing: When getLocationWeather is called, return a specific Weather object
    Weather stubbedWeather = new Weather();
    stubbedWeather.setTemp(25.0);
    stubbedWeather.setWindSpeed(10.0);
    when(weatherProvider.getLocationWeather(anyDouble(), anyDouble())).thenReturn(stubbedWeather);

    // Test the createIncident method
    Incident incident = new Incident(null, new Date(), null,"Turbine faulty", "Turbine is not generating any power","Assigned", 38.8951,  -77.0364, null );
    String guid = incidentManager.createIncident(incident);

    // Verify that getLocationWeather was called
    verify(weatherProvider, times(1)).getLocationWeather(anyDouble(), anyDouble());

    // Verify that the incident's weather is set based on the stubbed data
    assert 25.0 == incident.getWeather().getTemp();
    assert 10.0 == incident.getWeather().getWindSpeed();

}

Here we use Mockito to stub the result of the <span class="pink">getLocationWeather</span> method and mock an instance of the interface.

Execute the test by selecting the green "run" icon next to the test method declaration:

Executing the test by selecting the green "run" icon next to the test method declaration:

If all is well, the test will pass:

Test result: passed

How to mock an API response in a private method in Java

We've used stubbing to get the <span class="pink">Weather</span> object, but can we further mock and stub the JSON API response from the <span class="pink">callAPI()</span> method? Mocking a private method is considered bad practice and can lead to tightly coupled code, but we can consider two options:

  1. Use PowerMockito, a mocking framework that extends the functionality of Mockito. Providing steps for using PowerMockito is beyond the scope of this tutorial.
  2. Change the scope of the method for testing purposes.

Let's take a closer look at the second workaround. In <span class="pink">OpenWeatherProvider.java</span>, change the scope of <span class="pink">callAPI()</span> from <span class="pink">private</span> to <span class="pink">public</span>:


public JSONObject callAPI(String url) {

The unit test we'll create uses a "spy". A spy object (or partial mock) is similar to a mock, but where a mock mocks all methods by default, a spy object executes the underlying methods of the actual object, unless explicitly stubbed.

Create the following unit test below the <span class="pink">testCreateIncident()</span> method:


@Test
public void mockAPIResponse() {
    // Create a spy for the openWeatherProvider
    OpenWeatherProvider spyProvider = spy(new OpenWeatherProvider());

    // Stubbing: When callAPI is called, return a specific JSONObject
    JSONObject stubbedJson = new JSONObject("{\\"current\\":{\\"temp\\":25.0,\\"wind_speed\\":10.0}}");
    doReturn(stubbedJson).when(spyProvider).callAPI(anyString());

    // Test the getLocationWeather method using the spy
    double lat = 38.8951;
    double lon = -77.0364;
    Weather weather = spyProvider.getLocationWeather(lat, lon);

    // Verify that callAPI was called with the correct URL
    verify(spyProvider, times(1)).callAPI(anyString());

    // Assert the result based on the stubbed data
    assert 25.0 == weather.getTemp();
    assert 10.0 == weather.getWindSpeed();
}

As we did in the previous unit test, here we use stubbing to return a certain JSON response without calling the OpenWeather REST API. You can now run the test and observe the outcome.

How to stub a method to throw an exception

To effectively test our exception handling, we can set up stubbing behavior to throw an exception when a certain method is called on a mock or spy.

In the following example, we will use stubbing on the <span class="pink">getLocationWeather()</span> method to return the dreaded <span class="pink">NullPointerException</span> to see if the <span class="pink">createIncident()</span> method will successfully handle the error.

Create the following unit test below the <span class="pink">mockAPIResponse()</span> method:


@Test
public void testExceptionHandling() {
    doThrow(new NullPointerException("Simulated NullPointerException"))
            .when(weatherProvider).getLocationWeather(anyDouble(), anyDouble());

    Incident incident = new Incident(null, new Date(), null, "Turbine faulty", "Turbine is not generating any power", "Assigned",1.0,  -1.0, null);
    String guid = incidentManager.createIncident(incident);

    // Let's verify the call was made to getLocationWeather()
    verify(weatherProvider).getLocationWeather(1.0,  -1.0);

    assert guid == "";
}

When the test is executed and passes, you should see the following output:

SEVERE: Null pointer exception thrown in createIncident()

We can verify that the <span class="pink">NullPointerException</span> was thrown and assess that it was handled successfully in the try-catch block of the <span class="pink">createIncident()</span> method.

Further tests

For good measure, let's create unit tests to test the modify and delete actions. We won't use stubbing in the next two tests, but we will use our mock <span class="pink">IncidentManager</span> object to call the methods.

Create the following two unit tests below the <span class="pink">testExceptionHandling()</span> method:


@Test
public void testModifyIncident() {

    Incident incident = new Incident(null, new Date(), null, "Turbine faulty", "Turbine is not generating any power", "Assigned",38.8951,  -77.0364, null );
    String guid = incidentManager.createIncident(incident);

    incident.setGuid(guid);
    incident.setTitle("Three turbines faulty");
    // Test the modifyIncident method
    boolean modified = incidentManager.modifyIncident(incident);

    assert modified == true;
}

@Test
public void testDeleteIncident() {

    int listSize = incidentManager.findAllIncidents().size();

    Incident incident = new Incident(null, new Date(), null, "Turbine faulty", "Turbine is not generating any power", "Assigned",38.8951,  -77.0364, null );
    String guid = incidentManager.createIncident(incident);

    // Test the deleteIncident method
    incidentManager.deleteIncident(guid);
    int listSizeAfterDelete = incidentManager.findAllIncidents().size();

    // Assertions or verifications based on the expected behavior
    assert listSize == listSizeAfterDelete;
}

How to mock static methods and classes in Java

The Mockito framework primarily provides the functionality to mock instance methods. As with private methods, Mockito can't mock static methods and classes, and it's bad practice to do so. However, there are times we might need to mock a static method or class. You can consider one of three approaches:

  1. Use PowerMockito.
  2. Refactor the code as we did previously to test the private method.
  3. Where possible, wrap a static method in a non-static method and mock the non-static method.

Adding Unlogged to the project

Now that we have a better understanding of mocking, let's use Unlogged to mock and test parts of the help-desk application.

First, add the <span class="pink">@Unlogged</span> annotation to <span class="pink">HelpdeskApplication.java</span> just before the <span class="pink">main()</span> method:


@Unlogged
 public static void main(String args[]) {
     MinotorThread thread = new MinotorThread();
     thread.start();
 }

Next, import the Unlogged package by adding the following import statement just below the package statement in <span class="pink">HelpdeskApplication.java</span>:


import io.unlogged.Unlogged;

Now run the following command in the terminal:


mvn clean


We'll run the project in debug mode. In IntelliJ IDEA, make sure you are viewing the <span class="pink">HelpdeskApplication.java</span> file and select Current File from the dropdown list at the top-right. Click the debug icon (the green bug icon).

The application is now running in debug mode and debug output will show in the lower section of the IDE. Here we see the list size being printed every two seconds:

Debug output shown in the lower section of the IDE. The user can see the list size being printed every two seconds.

Direct invocation of methods with Unlogged

Let's see how the direct invoking of methods works with Unlogged by testing the <span class="pink">createIncident()</span> method.

Open <span class="pink">IncidentManager.java</span> and scroll down to the <span class="pink">createIncident()</span> method.

Notice the light blue box icon next to the line number:

Cube gutter icon indicating that a method can be invoked directly from the plugin interface

Click this icon to open the Unlogged direct invocation options:

Opening the unlogged direct invoke options

Here we can see that Unlogged has created the <span class="pink">Incident</span> object as JSON for us, and we can change the fields as we need.

We'll modify some fields in the method's arguments. Set the arguments as below (notice that we've omitted <span class="pink">"guid" : "string"</span> and the <span class="pink">weather</span> object):


{
  "dateTimeCreated" : 1702480258996,
  "customerAccountNo" : "A187746638-11",
  "title" : "Turbine fault",
  "description" : "Turbine not generating any power",
  "feedback" : "",
  "latitude" : 38.8951,
  "longitude" : -77.0364
}

Click the Execute method button.


Once the method has executed, you should have received an empty string and the following output on your running application log:

Dec 13, 2023 5:15:50 PM org.sample.application.IncidentManager createIncident SEVERE: Null pointer exception thrown in createIncident()

This is expected behavior. Since we do not have a valid API key, a null pointer exception is generated in the <span class="pink">getLocationWeather()</span> method of <span class="pink">OpenWeatherProvider</span>.

Creating a mock with Unlogged

Since this is a mocking tutorial, let's create a mock and stub the response from the <span class="pink">getLocationWeather()</span> method.

Open <span class="pink">IncidentManager.java</span> and locate the <span class="pink">createIncident()</span> method. Notice the purple ghost icons next to some of the line numbers:

Purple ghost gutter icons indicating that a line can be mocked

Unlogged automatically identifies the lines of code that can be mocked and places the ghost icon next to those lines.

Select the ghost icon next to the <span class="pink">weatherProvider.getLocationWeather()</span> method to open the mock editor:

Mocking Interface opening on clicking on the ghost icon

We can set the stub on the <span class="pink">weatherProvider</span> mock and generate a valid <span class="pink">Weather</span> object that will be added to the <span class="pink">Incident</span> object and then added to the list.

Change the values of <span class="pink">temp</span> to <span class="pink">28</span> and <span class="pink">windSpeed</span> to <span class="pink">70</span> in the JSON object, and select Save. The mock editor closes, and we get a confirmation of the mock created:

Mock created confirmation notification from the unlogged plugin interface

Now we can select the blue icon on our <span class="pink">createIncident()</span> method as we did previously to retry the incident creation. This time we receive a valid <span class="pink">guid</span> value back from the invocation.

Notice that the running application output now tells us that the incident list has a size of one incident. An incident has been successfully created and added to the incident list.


INFO: Incident list monitor, current list size = 1

Now let's see if we can retrieve the created incident details to confirm the values of the weather mock we configured.

We can directly invoke either the <span class="pink">findIncident()</span> or <span class="pink">findAllIncidents()</span> method. We'll use the <span class="pink">findAllIncidents()</span> invocation. Select the blue block next to the <span class="pink">findAllIncidents()</span> method, and select Execute method.

Image of the response confirming there is one incident returned in an array object.

From the response, we can confirm there is one incident returned in an array object.

We can also verify that our stubbing and mock for the <span class="pink">getLocationWeather()</span> method worked, as the <span class="pink">temp </span>and <span class="pink">windSpeed</span> values are the same as what we set on the mocked object.

Generating unit tests with Unlogged

With Unlogged, we can generate unit test cases in the Direct Invoke or Replay windows.

On the Direct Invoke tab, click the Create JUnit Boilerplate button to generate a unit test case boilerplate with arbitrary test values. You can modify the values to suit your test case.

To generate a unit test with the values we used in the previous direct execution, click the Generate JUnit Test button in the Replay tab. Generating your test cases this way will ensure the actual values you used in the execution are used in the unit test code.

Click the blue direct invocation icon next to the <span class="pink">createIncident()</span> method to open the direct invoke window.

Gif showing how to instantly create a unit test for a method recently direct invoked

You can select the Create JUnit Boilerplate button on the Direct Invoke tab or the Generate JUnit Test button on the Replay tab to open the settings dialog for our unit test. We can keep the default settings. Click Save.

Unlogged Unit test feature settings dialogue snapshot

Our unit test has now been created:


package org.sample.application;

import static io.unlogged.UnloggedTestUtils.*;
import static org.mockito.ArgumentMatchers.*;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.Exception;
import java.lang.String;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

public final class TestIncidentManagerV {
  private IncidentManager incidentManager;

  private WeatherProvider weatherProvider;

  private ObjectMapper objectMapper = new ObjectMapper();

  @Before
  public void setup() throws Exception {
    weatherProvider = Mockito.mock(WeatherProvider.class);
    incidentManager = new IncidentManager();
    injectField(incidentManager, "weatherProvider", weatherProvider);
  }

  @Test
  public void testMethodCreateIncident() throws Exception {
    //
    Weather locationWeather = objectMapper.readValue("{\\"temp\\": 0, \\"windSpeed\\": 0}", Weather.class);
    // org.sample.application.Weather locationWeather = weatherProvider.getLocationWeather(2 arguments);
    Mockito.when(weatherProvider.getLocationWeather(eq(0.13395229242079387D), eq(0.8408604953143396D))).thenReturn(locationWeather);
    //
    Incident incident = objectMapper.readValue("{}", Incident.class);
    String string = incidentManager.createIncident(incident);
    String stringExpected = null;
    Assert.assertEquals(stringExpected, string);
  }
}

We need to make some changes to the code before we run the unit test.

First, make sure that the following import has been added at the top of the newly generated unit test file:


import org.mockito.Mockito;

Next, change the mocked object's behavior by modifying the stubbed values for our <span class="pink">locationWeather</span> object:


Weather locationWeather = objectMapper.readValue("{\\"temp\\": 20, \\"windSpeed\\": 60}", Weather.class);

Now change the <span class="pink">Mockito.when()</span> statement so that the stubbing is applied on any double argument input:


Mockito.when(weatherProvider.getLocationWeather(anyDouble(), anyDouble())).thenReturn(locationWeather);

Finally, we need to modify the assert part of our test. For this example, we would rather assert that the weather object has the same values as were stubbed. Locate the following statements:


String stringExpected = null;
Assert.assertEquals(stringExpected, string);

And replace them with this:


// Let's assert the stubbed values were returned as expected
Assert.assertEquals(incident.getWeather().getTemp(), 20.0d, 0.0);
Assert.assertEquals(incident.getWeather().getWindSpeed(), 60.0d, 0.0);

We can now run our test and observe the result as we did with previous test runs.

Shardul Lavekar
January 17, 2024
Use Unlogged to
mock instantly
record and replay methods
mock instantly
Install Plugin