Blog
navigate_next
Java
Best Java Mocking Frameworks - mockito vs junit vs powermock vs wiremock
Pratik Dwivedi
March 28, 2024

A Comprehensive Guide to Java Mocking Frameworks

Java Mocking Frameworks were developed with the idea of unit-testing Java classes to find subtle errors and improve their performance. As you cannot test the whole of your system every time, and you would need to zoom in and pinpoint errors inside specific classes and remove them, Unit testing was introduced for this.

Unit testing is done to catch bugs early on in the development phase, ease refactoring, and ensure that a method conforms to the contract( parameters and return types) it proposes.

Testing and Mocking frameworks and Testing libraries can be categorized as follows: -

  • Traditional Unit testing: You test real objects using actual resources (e.g., database connections and API calls) in your system. While this gives an accurate picture of how your methods perform, it might be resource-intensive, and you might have to wait to test a module until all the resources it uses are available. Traditional Unit testing is more of a state transition or state-based testing, where you get a good idea of where your system stands in terms of delivering functionality and availability of resources. Example: JUnit, TestNG, etc.
  • Mocking or Mock-driven testing: Mocking frameworks mock some of the resources your method might need for testing. In other words, you can test your code even if all the resources it needs are not available. Mocking enables behavior-driven testing; assertions are run by using mocks of objects. You can rigorously test your objects even if the third-party resources they use are not available. This type of testing can be implemented anytime and can ensure good quality code in the very early stages of development. Mocking follows a developer-focused approach. Example: Mockito, EasyMock, and JMock, etc.
  • Acceptance test–driven development (ATDD): ATDD is a collaborative process where developers, testers, and business representatives come together and communicate vigorously to ensure that expectations are specified clearly and system boundaries (including potential pitfalls and security measures) are defined clearly. It's normally done before coding begins but can also be done during the user acceptance testing phase. ATDD follows a customer/end-user-focused approach. Examples: Concordion, Serenity, etc.
  • API mocking or Integration testing: Used to test your APIs and how they interact with other API-based services. Instead of unit tests, here the API interfaces and their interactions are put to the test. It allows for REST as well as SOAP-based testing. You can rigorously test your Web APIs via mock servers and also run load tests. Example: Wiremock, Katalon.
  • Automated Testing: Tests are automated, input values are automatically injected, and ranges of return values are defined. Developers don’t need to write boilerplate code for menial tests, and the performance of the system as a whole can also be ascertained. This can consist of a mix of the above testing methodologies. Example: Artos, Selenium, etc.

From Java 3 onwards, two important additions to Java enabled the creation of mocking frameworks and enabled the conduction of mock tests on Java code, these were :

  1. Proxy classes - The Proxy class is the base class for all dynamic proxy classes and provides static methods for creating such dynamic proxy classes.
  2. CGlib(Code Generation Library) library is a bytecode instrumentation library that allows manipulating or creating classes after the compilation phase of a program.Using CGlib you can easily add new classes to an already running Java program and instrument lazy loading for java classes or objects.Frameworks like Spring and Hibernate use it heavily, but it found very good use in mocking Java classes and replacing methods with their empty or dummy implementations. Mocking frameworks like Mockito and JMock use it to deliver their functionality.

Core to the creation of mock classes are the dynamic proxy classes, they act as proxies for the methods/interfaces that you need to test. Dynamic proxy classes allow you to mimic the behavior of actual class implementations though in a predefined manner. E.g. If your code uses a payment gateway or a weather API, you don't need to call the actual API or make the expensive database connection. Your proxy implementations will behave just like the actual class and give expected outputs to given inputs.

So, this lets you test other classes that are dependent on these classes, without actually making the connections, thereby making the process simpler and predictable. Frameworks like JUnit and Mockito use this proxy feature along with Java Reflection in order to create mock objects for a given interface.

Now this also means that while using Proxy class and reflection, you cannot mock test your constructors, final classes and final methods, static blocks of code and static methods, etc. The Java compiler would simply not allow doing so. Also, usage of static code blocks and final classes was discouraged as it was not considered a good practice, hence frameworks like Mockito avoided these features. But this limited the testing and mocking frameworks from testing constructors, final methods, static methods and static blocks of code.

To ease things further, starting from Java 5 Bytecode Instrumentation was introduced, which allows applications to redefine classes loaded in the JVM. So whenever a running class inside the JVM is to be mocked, behavior of its regular methods is replaced by mock behavior for a limited time. Additional bytecode is added to or removed from methods, to change their behavior to be more predictable, constrained or fixed. This can then be used for a variety of purposes, including performance monitoring, security checks, debugging and profiling.

The aim of this post is to compare four different testing frameworks, the functionality that they deliver and the different approaches that they follow.  JUnit and Mockito are primarily unit testing frameworks, that are used to test classes in isolation to other classes. PowerMock and WireMock are more specialized frameworks that can test final/static methods, constructors and static code blocks;  as well as mock APIs by using their own dummy servers.

As our aim is to compare the test frameworks amongst themselves, we take a simple math class that calculates functions like <span class="pink">min()</span> / <span class="pink">max()</span> / <span class="pink">sqrt()</span> / <span class="pink">pow()</span> etc.

e.g. Your class could look something like : -


import java.lang.Math;

public class MathAPI {

    public MathAPI() {
    }
    // Function to find the minimum of two integers
    public int min(int a, int b) {
        return Math.min(a, b);
    }

    // Function to find the maximum of two integers
    public int max(int a, int b) {
        return Math.max(a, b);
    }

    // Function to calculate the square root of an integer
    public double squareRoot(int num) {
        return Math.sqrt(num);
    }

    // Function to calculate the power of an integer
    public double power(int base, int exponent) {
        return Math.pow(base, exponent);
    }

    // Function to find the floor value of a float
    public float floor(float num) {
        return (float) Math.floor(num);
    }

    public void main(String[] args) {
        // Example usage
        int a = 5, b = 8;
        System.out.println("Min: " + min(a, b));
        System.out.println("Max: " + max(a, b));
        System.out.println("Square Root: " + squareRoot(a));
        System.out.println("Power: " + power(a, 2));
        float c = 3.14f;
        System.out.println("Floor: " + floor(c));
    }
}

Now lets test this class via different frameworks, in order to compare them.

JUNIT:JUnit, as the name suggests is used for unit testing your java code. A "Unit" here means a piece of code that does a useful piece of work, generally a function/method. Using JUnit we can test each method of this class, as well as define expected behavior via Junit annotations. You will need to add a dependency in your pom.xml, e.g.


<dependencies>  
	 <dependency> 
		 <groupId>org.junit.jupiter</groupId>  
		 <artifactId>junit-jupiter-api</artifactId>  
		 <version>5.9.0</version>  
		 <scope>test</scope>  
	 </dependency>
 </dependencies>


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

public class MathAPITestJUnit {
    MathAPI mathAPI = new MathAPI();

	@BeforeAll
	public static void classLevelSetUpRunsOnce() throws java.lang.Exception {
		// Runs only once for class level initializations, before any test is executed. 
        System.out.println("before class");
    }  
	
	@BeforeEach  
    public void beforeEachTestSetUp() throws java.lang.Exception {
	// Runs before each test , specify common pre-test setup code here.
        System.out.println("before");  
    }  
	
    @Test
    @Timeout(10000)
    public void testMinFunction() {
		//Will fail if it takes longer than 10000 milliseconds

        assertEquals(3, mathAPI.min(3, 5));
        assertEquals(-2, mathAPI.min(0, -2));
    }

    @Test
    public void testMaxFunction() {
        assertEquals(5, mathAPI.max(3, 5));
        assertEquals(0, mathAPI.max(0, -2));
    }

    @Test
    public void testSquareRootFunction() {
        assertEquals(2.0, mathAPI.squareRoot(4),  0.001);
        assertEquals(6.0, mathAPI.squareRoot(36), 0.001);
        assertEquals(Double.NaN, mathAPI.squareRoot(-1), 0.001);
    }

    @Test
    public void testPowerFunction() {
        assertEquals(8, mathAPI.power(2, 3));
        assertEquals(1, mathAPI.power(5, 0));
        //assertEquals(1.0 / 16, MathAPI.power(2, -4), 0.001);
    }

    @Test
    public void testFloorFunction() {
        assertEquals(3, mathAPI.floor(3.8f));
        assertEquals(-4, mathAPI.floor(-3.2f));
    }
	
	@AfterEach  
    public void tearDown() throws java.lang.Exception {
	//Runs after each test 
        System.out.println("Runs after each test");  
    }  
  
    @AfterAll 
    public static void tearDownAfterClass() throws java.lang.Exception {
	//Runs after all tests in this class are executed 
        System.out.println("Runs after class execution");  
    }  	
}

The above code is quite self explanatory, we provide one function/method each to test the methods of the MathAPI class,  annotate it with @Test annotation, and specify certain behavior with annotations like @BeforeAll, @BeforeEach and @AfterEach etc.  I used Intellij Idea to run the above code, If everything goes well, you will see something like the below screenshot.

IDE window displaying a JUnit test file named MathAPITestJUnit.java, with the test code visible in the editor. The 'Run' panel at the bottom shows the results of various test functions such as testFloorFunction, testMaxFunction, and testMinFunction, all marked as passed. Console output includes messages indicating test setup and teardown methods

JUnit is the oldest and simplest of the frameworks that we discuss. Next, lets see some Mockito code.  Lets assume that the <span class="pink">MathAPI</span> class discussed above is not available for some reason, either its not complete or its locked or anything. But we need to test our code that uses the <span class="pink">MathAPI</span> class. Mockito uses the mocking approach which was discussed above, instead of calling the actual methods, Stubs are created for those methods which act as mocks for actual methods. Mockito allows defining expectations and behavior of these mock objects, enabling the simulation of specific scenarios during testing. This is very useful when external objects/systems are not easily/ directly available for testing.

Add the following dependency to your XML config file : -


<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.4.0</version>
    <scope>test</scope>
</dependency>

Lets dive into some code to make things clear.


import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

public class MathAPITestMockito {

    @Test
    public void testMinFunction() {
        // Mockito enables the use of a mock abject instead of a real MathAPI object. 
        MathAPI mathAPI = Mockito.mock(MathAPI.class);

        // Define the inputs
        int num1 = 5;
        int num2 = 10;
        // Set up the mock behavior for the min function
        when(mathAPI.min(num1, num2)).thenReturn(Math.min(num1, num2));
		
        // Call the methosd to be tested. 
        int result = mathAPI.min(num1, num2);
        // Assert
        assertEquals(Math.min(num1, num2), result);
    }

    @Test
    public void testMaxFunction() {
        MathAPI mathAPI = Mockito.mock(MathAPI.class);
        int num1 = 5, num2 = 10;
        // Set up the mock behavior for the max function
        when(mathAPI.max(num1, num2)).thenReturn(Math.max(num1, num2));
        
		int result = mathAPI.max(num1, num2);
        // Assert or Test the output 
        assertEquals(Math.max(num1, num2), result);
    }

    @Test
    public void testSquareRootFunction() {
        MathAPI mathAPI = Mockito.mock(MathAPI.class);

        int number = 25;
        when(mathAPI.squareRoot(number)).thenReturn(Math.sqrt(number));


        double result = mathAPI.squareRoot(number);
        assertEquals(Math.sqrt(number), result);
    }

    @Test
    public void testPowerFunction() {

        MathAPI mathAPI = Mockito.mock(MathAPI.class);

        int base = 2, exponent = 3;

        // Set up the mock behavior for the power function
        when(mathAPI.power(base, exponent)).thenReturn(Math.pow(base, exponent));


        double result = mathAPI.power(base, exponent);
        assertEquals(Math.pow(base, exponent), result);
    }

    @Test
    public void testFloorFunction() {

        MathAPI mathAPI = Mockito.mock(MathAPI.class);

        float number = 7.8f;
        when(mathAPI.floor(number)).thenReturn((float)Math.floor(number));

        double result = mathAPI.floor(number);
        assertEquals(Math.floor(number), result);
    }
}

As you can see Mockito code is a bit clearer as you directly create a mock class of the class to be tested , Mockito.mock(<span class="pink">MathAPI.class</span>), and you use keywords like "when", "thenReturn" , "doReturn", "thenThrow" etc. The assertEquals statements are used to check if the actual results match the expected results.

Here we create what is conceptually known as a "Stub", a Stub is a class that mocks/mimics the behavior of a real class, but sends preprogrammed return values or throws pre-programmed exceptions. It just implements a subset of a class's features/implementation, so as to facilitate and ease testing.  e.g. A typical stub is a database connection that allows you to mimic querying scenarios without connecting to a real database.

IDE window displaying a Mockito test file named MathAPITestMockito.java, with the test code visible in the editor. The test method testPowerFunction() is shown, demonstrating mock setup for a power function calculation. The 'Run' panel at the bottom shows the results of various test functions such as testFloorFunction, testMaxFunction, and testPowerFunction, all marked as passed. Console output confirms all tests completed successfully with exit code 0

In Mockito, we implement stubbing by configuring a mock and defining the actions to be taken when a specific method of the mock is invoked.

But the above two frameworks have some serious limitations,  they don’t let you test constructors, final methods, static methods, and static blocks of code.  As discussed earlier,  Java6 introduced bytecode instrumentation, which allowed applications to redefine classes loaded in the JVM. Hence mocking final classes, constructors, and private methods became possible.

JMockit and PowerMockito take advantage of this feature, by using the redefineClasses(ClassDefinition...) method, in the java.lang.instrument package. The redefineClasses method takes as parameter, an array of classes to redefine with corresponding definitions, and replaces existing classes (with the same name) with supplied class files, which in-turn facilitates replacing of class files to be tested, with their mock class files. The mock class files contain predefined (mocked) behavior to facilitate further testing of their dependent classes. Since all this is done with compiled classes inside the JVM, the compiler restrictions do not come into the picture and hence one can mock final classes, constructors and private methods too.

Also, this does not disturb the old method definitions in the active stack frames, those active frames continue to run the bytecode of the original method. The new (replaced) method definitions are used for fresh invokes only. This instrumentation  allows interdependent changes to more than one class at the same time, and does not cause initializers to be run, and keeps the values of static variables as they were prior to the call, allowing stability and predictability.

Now lets assume that the actual <span class="pink">MathAPI</span> class has declared methods <span class="pink">min()</span> and <span class="pink">max()</span> as static; <span class="pink">pow()</span> and <span class="pink">sqrt()</span> as final, hence its instantiation is not possible using Mockito or JUnit.

So, we use PowerMock, which extends Mockito to provide additional capabilities like mocking static methods, final classes, final methods, private methods, constructors, and removal of static bunch of code. PowerMock framework uses a custom class loader and bytecode manipulation techniques to achieve its aim.

Lets first assume that we have another class, very similar to the <span class="pink">MathAPI</span> class discussed above, the only difference being that our new <span class="pink">MathAPISF</span> class has some static and final methods.


    import java.lang.Math;

    public class MathAPISF {

        public MathAPISF() {
        }
        // Function to find the minimum of two integers
        public static int min(int a, int b) {
            return Math.min(a, b);
        }

        // Function to find the maximum of two integers
        public static int max(int a, int b) {
            return Math.max(a, b);
        }

        // Function to calculate the square root of an integer
        public final double squareRoot(int num) {
            return Math.sqrt(num);
        }

        // Function to calculate the power of an integer
        public final double power(int base, int exponent) {
            return Math.pow(base, exponent);
        }

        // Function to find the floor value of a float
        public final float floor(float num) {
            return (float) Math.floor(num);
        }

        public static void main(String[] args) {
            // Example usage
            int a = 5, b = 8;
            System.out.println("Min: " + min(a, b));
            System.out.println("Max: " + max(a, b));
            MathAPISF sf = new MathAPISF();
            System.out.println("Square Root: " + sf.squareRoot(a));
            System.out.println("Power: " + sf.power(a, 2));
            float c = 3.14f;
            System.out.println("Floor: " + sf.floor(c));
        }
    }

While using Powermock, we will use <span class="pink">mockStatic()</span> to mock all static methods of a class , <span class="pink">mock()</span> for all final methods or <span class="pink">spy()</span> to partially mock only a few methods of a class. More details here.



<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-core</artifactId>
    <version>2.0.9</version>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>2.0.9</version>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>2.0.9</version>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-testng-common</artifactId>
    <version>2.0.9</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.12.4</version>
</dependency>
<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.10.14</version>
</dependency>
<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy-agent</artifactId>
    <version>1.10.14</version>
</dependency>
<dependency>
    <groupId>org.objenesis</groupId>
    <artifactId>objenesis</artifactId>
    <version>3.0.1</version>
</dependency>



import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.annotations.BeforeTest;

import static org.junit.Assert.assertEquals;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.powermock.api.mockito.PowerMockito.verifyNew;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ MathAPISF.class }) // FQCN used here, accepts array of classes or wildcards, e.g. com.my.static.*

public class PowerMockMathAPITest extends PowerMockTestCase{

    @BeforeTest
    public void init() {
        initMocks(this);
    }

    @Test
    public void testMathAPI() throws Exception {
        // Using Powermock to mock static methods in the MathAPI class
        PowerMockito.mockStatic(MathAPISF.class);

        // Setting up an expectation, Mocking min(0, x) function
        PowerMockito.when(MathAPISF.min(0, 10)).thenReturn(0);

        // Mocking the max(x, y) function
        PowerMockito.when(MathAPISF.max(5, 8)).thenReturn(8);

        // Call the methods in your class, invocations are delegated to the mock class created via mockStatic() above
        // For example:
        int result1 = MathAPISF.min(0, 10);
        int result2 = MathAPISF.max(5, 8);

        // Verify that the methods in your class are working as expected
        assertEquals(0, result1);
        assertEquals(8, result2);

        // Optionally, verify that static methods in the Math class were called n times [ specified via times(n) ]
        PowerMockito.verifyStatic(MathAPISF.class, Mockito.times(1));//checks if the immediately following method call(in the next line) took place
        MathAPISF.min(0, 10);
        PowerMockito.verifyStatic(MathAPISF.class, Mockito.times(1));
        MathAPISF.max(5, 8);
    }
    /* ==================================================================== */
    /** To test the final methods of the class, we use PowerMockito.mock(class) instead of mockStatic() method.
     If your class has a no-argument constructor, setup an expectation that whenever that no-arg constructor is invoked, a mock  instance should be returned rather than a real one. This way we test a contructor also.
     */
    @Test
    public void testMathAPIFinal() throws Exception {
        MathAPISF mathMock = PowerMockito.mock(MathAPISF.class) ;
        PowerMockito.whenNew(MathAPISF.class).withNoArguments().thenReturn(mathMock);

        //Next, instantiate MathAPI using the default no-arguments contructor, and verify
        MathAPISF mathMock2 = new MathAPISF();
        verifyNew(MathAPISF.class).withNoArguments();

        //Set the expectation for the final method(s); this will be checked later via assertEquals() method.
        when(mathMock2.squareRoot(25)).thenReturn(5.0);
        when(mathMock2.power(2, 3)).thenReturn(8.0);

        // Execute the final methods
        int result1 = (int) mathMock2.squareRoot(25);
        int  result2 = (int) mathMock2.power(2, 3);

        // Assertions to confirm(verify) that the methods was indeed called, and the values setup by the mocking expectation were actually returned
        assertEquals(5, result1);
        assertEquals(8, result2);
    }
}

If you want to just mock a particular final/static method in a class, and not the whole class, you can use PowerMock's <span class="pink">spy()</span> function.

Here we can instantiate the class as usual, and then tell Powermock to "SPY" it.


	@Test
	public void powermockSpyFinal() throws Exception {

		MathAPISF myMathMock2 = PowerMockito.spy(new MathAPISF());
		//Now, you can call the class's static and final methods using the mock object named myMathMock.
		PowerMockito.when(myMathMock2.power(2, 3)).thenReturn(8.00);
		double result2 = myMathMock2.power(2, 3);
		// Assertions to confirm(verify) that t   he methods was indeed called, and the values setup by the mocking expectation were actually returned
		assertEquals(8.00, result2, 1);
	}

	@Test
	public void powermockSpyFloor() throws Exception {

		MathAPISF myMathMock3 = PowerMockito.spy(new MathAPISF());
		//doReturn(10).when(myMathMock3).min(0, 10);
		PowerMockito.when(myMathMock3.floor(7.9f)).thenReturn(7.0f);
		float valueReturnedByMethod = myMathMock3.floor(7.9f);
		//You can then use Mockito to verify if the method was called.
		Mockito.verify(myMathMock3).floor(7.9f);
		//And then check if the value returned by the final ( or static) method was indeed according to the expectation.
		assertEquals(7.0f, valueReturnedByMethod,1);

	}

If testing a private method, remember that private methods are always called by other methods of the same class, which is a prescribed design practice. So, while testing, you need to call the method that calls the private method, and not the private method(s) directly. The corresponding method call will be <span class="pink">verifyPrivate()</span>. If you want to test a no-argument constructor only, the corresponding method call will be <span class="pink">verifyNew()</span> .

A word of caution here, If you’re using PowerMock and running it on Java 17, you will need to add the following JVM options to your IDE or command line.e.g.


--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED

Run/Debug Configurations window in an IDE, showing the configuration settings for the test named PowerMockMathAPITest. The configuration includes the build and run command using Java 17 SDK with several --add-opens options to grant unnamed module access to specific Java modules. Options to modify, run, or cancel the configuration are located at the bottom of the window.

That’s because starting from Java9 and upwards, strong encapsulation was implemented , which mandated that if you wish to extend or introduce bytecode instrumentation, either : -

  • the type has to be public
  • the owning package has to be exported

So, if the class hierarchy you are attempting to instantiate contains classes in java.lang or java.util etc, you have add the above so that PowerMock can mock static and final methods down the line.

Lastly we discuss WireMock.WireMock is a library for stubbing and mocking HTTP services, and it's typically used for testing.WireMock solves some issues that might occur if we're testing on a real HTTP/HTTPS server:-(i) Testing on a real server could be time and resource consuming.(ii) It will need an internet connection and can cause delays as requests and responses run back and forth.(iii) It could overwhelm/ crash the remote server if tests are run on a large scale or load testing is done.

WireMock creates a proxy web-server for you, which you can connect to just like your actual web-server, and test your API calls. Lets get started with some code.

Include the following dependency in your POM.xml, to use Wiremock


<dependency>
    <groupId>com.github.tomakehurst</groupId>
    <artifactId>wiremock-jre8-standalone</artifactId>
    <version>2.31.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>

Though WireMock can run as a wrapper around other frameworks like JUnit and as a library, we will focus on its functioning as a standalone server, in order to highlight its unique features. Lets assume that out <span class="pink">MathAPI</span> class exposes its methods via a REST API.

Below is an example WireMock test case in Java for testing the mentioned Math API calls.


import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import wiremock.org.apache.http.HttpResponse;
import wiremock.org.apache.http.client.methods.HttpGet;
import wiremock.org.apache.http.impl.client.CloseableHttpClient;
import wiremock.org.apache.http.impl.client.HttpClients;

import java.io.InputStream;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class MathApiIntegrationTestWireMock {

    private static WireMockServer wireMockServer;

    @BeforeAll
    public static void setup() {
        wireMockServer = new WireMockServer();
        wireMockServer.start();
        WireMock.configureFor("localhost", wireMockServer.port());
		// default port is 8080, you can specify any other port here, OR via @AutoConfigureWireMock annotation OR via --post command line parameter OR just set port to 0 so that wiremock chooses a random port which can then be referred via (wiremock.server.port)  endpoint property.
    }

    @AfterAll
    public static void tearDown() {
        wireMockServer.stop();
    }

    @Test
    public void testMinFunction() throws Exception {
        // Define the expected request and response for the min function, if your request has many query parameters you can consider using multiple  ".withQueryParam("parameter1", equalTo("value1"))" for readability. 
	
        stubFor(get(urlEqualTo("/MathAPI/min?param1=7¶m2=8"))
                .willReturn(aResponse()
                        .withStatus(200)
                        .withBody("{\"result\": 7}")
                ));
		// aResponse() is a static method in the WireMock class, used to create a new ResponseDefinitionBuilder object 
        // Your actual code to make the API call and assert the result goes here
		CloseableHttpClient testHttpClient = HttpClients.createDefault();
		HttpGet request = new HttpGet("http://localhost:8080/MathAPI/min?param1=7¶m2=8");
		 HttpResponse httpResponse = testHttpClient.execute(request);
		String responseString = HTTPResponseToString(httpResponse); 
		
        // Verify that the expected request was made
        verify(getRequestedFor(urlEqualTo("/MathAPI/min?param1=7¶m2=8")));
				
		// OR for an exact number of calls, use the code below 
		// verify(exactly(1), getRequestedFor(urlEqualTo("http://localhost:8080/MathAPI/min?param1=7¶m2=8")));
		assertEquals("{\"result\": 7}", responseString); 
    }

    @Test
    public void testMaxFunction() throws Exception {
        // Define the expected request and response for the max function
        stubFor(get(urlEqualTo("/MathAPI/max?param1=8¶m2=5"))
                .willReturn(aResponse()
                        .withStatus(200)
                        .withBody("{\"result\": 8}")
                ));
        
		// Make the API call
        CloseableHttpClient testHttpClient = HttpClients.createDefault();
		HttpGet request = new HttpGet("http://localhost:8080/MathAPI/max?param1=8¶m2=5");
		HttpResponse httpResponse = testHttpClient.execute(request);
		String responseString = HTTPResponseToString(httpResponse);

        // Verify that the expected request was made
        verify(getRequestedFor(urlEqualTo("/MathAPI/max?param1=8¶m2=5")));
		assertEquals("{\"result\": 8}", responseString); 
		
    }

    // Similar tests for squareroot(), power(), and floor() functions
	
	private String HTTPResponseToString(HttpResponse myResponse) throws Exception {
		InputStream responseStream = myResponse.getEntity().getContent();
		java.util.Scanner scanner = new java.util.Scanner(responseStream, "UTF-8");
		String responseString = scanner.useDelimiter("\\Z").next();
		scanner.close();
		return responseString;
	}
}

More details about specifying the expected responses when building Stubs, can be found here.

Below is the screenshot of what I see after successfully running some Wiremock tests.

IDE window displaying a Java test file named MathAPIIntegrationTestWireMock.java. The code defines a test method, testMinFunction, that uses WireMock to stub a GET request to a local URL and return a response with status 200 and a JSON body. The test file verifies the expected request URL and response. The Project pane on the left lists libraries and files in the TestingComparison project. Below the code editor, the Run pane shows test results, indicating that 2 out of 2 tests passed, with messages about a failed attempt to load a logger class."

WireMock is powerful and versatile, it can do browser proxying for request inspection and replacement, simulate stateful behavior, and allows for configuring fault injection and response delays. Wiremock can match any part of an incoming request against complex and precise criteria; and can record an replay the stubs. It has adapters and implementations for various technology stacks, including Python, .NET, Golang, and Rust.

Wiremock provides some advanced features like:-

  1. Response Redirect (permanent OR temporary)

stubFor(post(urlPathEqualTo("/min"))
.willReturn(permanentRedirect("someURL/MathAPI/calculateMin")));

       2. Response Templating

        3. Simulate delayed response using with FixedDelay(Integer milliseconds)


stubFor(post(urlEqualTo("/min"))
.willReturn(aResponse()
.withFixedDelay(400)
.withStatus(200)
.withBody("{\"result\": 7}")
));

  1. Simulate faults with

withFault.(withFault (EMPTY_RESPONSE/Fault.MALFORMED_RESPONSE_CHUNK/EMPTY_RESPONSE/RANDOM_DATA_THEN_CLOSE/CONNECTION_RESET_BY_PEER) ) 

  1. Use of "Fixtures" to simulate long responses, like dynamic CVG files or XML SOAP documents. You can create a separate folder named "fixtures" in your resources folder, and place the sample JSON/SVG/XML/Video file there. This sample file can contain the response body in it, and is then used for testing.

.willReturn(aResponse()
.withBody(FixtureHelpers.fixture("fixtures/my_test.json"))
.withHeader("Content-Type", "text/json")
.withStatus(200)));

  1. Proxying requests through to other hosts

Summarizing the above:

Comparison table of testing frameworks JUnit, Mockito, PowerMock, and WireMock. The table outlines differences in usage, accuracy, verification style, resource intensity, concurrency support, and syntax. JUnit tests real objects and requires all dependencies, suitable for multi-threaded testing. Mockito mocks objects selectively, with limited concurrency support. PowerMock handles unavailable resources with advanced behavior-driven testing but has concurrency issues. WireMock is used for API testing, mocks external services, and has limited concurrency support, using declarative syntax for request-response stubbing

Mockito/JUnit is suitable for unit testing most of your code, while PowerMock is useful only when trying to test code that needs its extra features like testing final/static blocks of code and private members/methods.

ALL the testing frameworks available, including the ones discussed above, have one serious drawbacks! You have to manually program and configure your tests; and collect data from those tests to figure out statistics and insights. This can be cumbersome and definitely drives down developer productivity. Unlogged is a tool that can automate all of this for you, and provide test performance statistics too. Using unlogged you can easily switch between these frameworks, use any one of them just by a few clicks to configure and run your testing procedures.

Unlogged is an Intellij plugin for automated regression testing, including record and replay for Java with assertions, mocking and code coverage.

With Unlogged there is no need to code anything, Unlogged records inputs and outputs of Java methods, enabling instant regression checks. You just need to add a dependency in your environment and Unlogged will start monitoring your method interactions behind the scenes.

Code editor showing the CustomerController class in Java with RESTful API endpoints defined using Spring annotations. The Unlogged panel on the right displays a test configuration for replaying a unit test, including settings for downstream call mocks, assertions, and sample data assertions such as customer ID, name, date of birth, email, and contact information.

Whether you're mocking or unit testing or integration testing, Unlogged will automate all these for you. You can seamlessly integrate into CI/CD pipelines.

Code editor displaying the CustomerController Java class with an Unlogged Mocks overlay window. The overlay shows settings for mocking API responses, specifically the customerService.fetchCustomerProfile method with one argument. Options include creating new mocks, editing mock definitions, and viewing mock responses for different parameters. The mock response editor displays details like customer ID, name, date of birth, email, and contact information in JSON format

Unlogged provides a holistic view of all statistics generated from your testing exercises, visually.

To conclude, we have discussed the unique characteristics of some testing frameworks and scenarios where they fit the best. We have also discussed that it's best to automate your testing processes to enhance developer productivity and ease rapid development cycles.

Thanks for your time.

Pratik Dwivedi
March 28, 2024
Use Unlogged to
mock instantly
record and replay methods
mock instantly
Install Plugin