Codementor Events

How to Mock Static Methods With Mockito

Published Apr 06, 2024
How to Mock Static Methods With Mockito

Introduction

Have you ever gotten stuck writing automated tests for a class because it was using static method calls? Although the best practice is to refactor the code so that it no longer uses static methods, in some cases this isn't possible. This is the case, for example, if the code in question does not belong to you. The Path and Files classes in the java.nio package are practical examples. In this article, you will learn how to mock static methods using the Mockito library.

The Application Under Test

The application we are going to test has three main classes:

  • SalesProcessorStatic.java: This class has a unique method compute that takes a list of strings representing the content of a CSV file. The String has the following structure "productCode;saleAmount". The compute() method must return the total sales of the input List.
  • SaleLineParser: This class takes a string in the form "productCode;saleAmount", parses it, and returns a Sale object.
  • Sale.java: is a simple Java record with two fields: public record Sale(String productCode, int saleAmount) { }

The SalesProcessorStatic.java class

package com.kbytes;

import java.util.List;
import java.util.function.Function;

public class SalesProcessorStatic {

    public SalesProcessorStatic(){
    }

    public int compute(List<String> sales){
        Function<String,Sale> toSale = SaleLineParserStatic::parse;
        return sales.stream()
                .map(toSale)
                .map(Sale::saleAmount)
                .reduce(Integer::sum).orElse(0);
    }
}

The Class to be Mocked: SaleLineParserStatic.java

package com.kbytes;

public class SaleLineParserStatic {
    private static final int COLUMN_COUNT = 2;

    public static Sale parse(String line){
        return parse(line,";");
    }
    public static Sale parse(String line, String delimiter){
        String[] saleData = line.split(delimiter,-1);

        if(isInvalidColumnCount(saleData))
            throw new IllegalArgumentException("The file must have "+COLUMN_COUNT+" columns!");

        if(isInvalidField(saleData))
            throw new IllegalArgumentException("The file data format is invalid!");

        return new Sale(saleData[0],Integer.parseInt(saleData[1]));
    }

    private static boolean isInvalidField(String[] saleData) {
        boolean isSaleInvalid = true;
        try{
            Double.parseDouble(saleData[1]);
            isSaleInvalid = false;
        }catch (NumberFormatException ex){
            ex.printStackTrace();
        }
        return saleData[0] == null || saleData[0].isEmpty() || isSaleInvalid;
    }

    private static boolean isInvalidColumnCount(String[] saleData) {
        return saleData != null && (saleData.length != COLUMN_COUNT);
    }
}

The Sale.java Record

public record Sale(String productCode, int saleAmount) { }

As you can see from this line Function<String,Sale> toSale = SaleLineParserStatic::parse, the compute() method of the SalesProcessorStatic class is making a static call to the parse() method of SaleLineParserStatic. To test the SaleProcessorStatic class independently of the SaleLineParserStatic, you have to mock the call to the static method SaleLineParserStatic.parse().

Step 1: Add the Maven Dependency

The only dependencies that you will need are JUnit 5 Engine and Mockito Core.
Add the following dependencies to your project:

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.9.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>5.11.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Step 2: Mock The Static Method with MockedStatic

Mocking static methods has been possible since Mockito 3.4.0. The procedure for mocking a static method is as follows:

  • Use the mockStatic() method of the Mockito class: mockStatic(SaleLineParserStatic.class)
  • Retrieve the object returned by the mockStatic() method in a variable of type MockedStatic: MockedStatic<SaleLineParserStatic> mockSaleLineParser = mockStatic(SaleLineParserStatic.class)
  • Configure the mock object: mockSaleLineParser.when(() -> SaleLineParserStatic.parse("P1;10")).thenReturn(sale1)

From the MockedStatic class Javadoc, you can read the following:

The static mock is released when this object's MockedStatic#close() method is invoked. If this object is never closed, the static mock will remain active on the initiating thread. It is therefore recommended to create this object within a try-with-resources.

Below is the complete test method for the SalesProcessorStatic class:

    @Test
void test_simple_compute(){
    //given
    List<String> sales = List.of("P1;10","P2;20");
    Sale sale1 = new Sale("P1",50);
    Sale sale2 = new Sale("P2",70);
    int expectedTotal = 120;
    try(MockedStatic<SaleLineParserStatic> mockSaleLineParser = mockStatic(SaleLineParserStatic.class)){
        mockSaleLineParser.when(() -> SaleLineParserStatic.parse("P1;10")).thenReturn(sale1);
        mockSaleLineParser.when(() -> SaleLineParserStatic.parse("P2;20")).thenReturn(sale2);
        //when
        SalesProcessorStatic salesProcessor = new SalesProcessorStatic();
        int salesTotal = salesProcessor.compute(sales);
        //then
        Assertions.assertEquals(expectedTotal,salesTotal);
    }
}

Given that the mock object is closed at the end of the try-with-resource block, the verifying part (Assertions) of your test must be within that same block.

If you need to mock different classes in the same test, you should add them to the try-with-resource block like this:

    try(MockedStatic<FirstClassWithStaticMethod> firstMock = mockStatic(FirstClassWithStaticMethod.class);
MockedStatic<SecondClassWithStaticMethod> secondMock = mockStatic(SecondClassWithStaticMethod.class)){
        //your code  
        }

Conclusion

In this quick tutorial, you learned how to mock static methods using Mockito. If you want to learn how to use this approach to mock access to the FileSystem, check out this article.
The full code of this tutorial can be found here on GitHub.

Originally published at https://nkamphoa.com

Discover and read more posts from Noel KAMPHOA
get started
post commentsBe the first to share your opinion
Show more replies