Search code examples
javajunitmockitojunit5

Java mockito test static method keeps the mock from the first test case in runs


I am trying to create multiple test cases with Mockito in Java with static mocks. They run fine when running separate but when I run the whole test class the mocked method which ran first (in this case shouldRequirePricingIfSucceeding) will also impact the other testcases (in this case shouldNotRequirePricingIfRequestFails will never fail). I have tried now to just put all the things I mock even in their own test methods but even that doesn't work.

Here is my code:

public class PricingService {
    public static final int PRODUCT_CODE = 10;
    private final ZoneId zoneId = ZoneId.of("UTC");
    private static final Logger LOGGER = LoggerFactory.getLogger(PricingService.class);
    private static final PricingOauthApi pricingOauthApi = PricingOauthApiFactory.create();
    private static final PricingApi pricingApi = PricingApiFactory.create();
    private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX");

    public boolean requiresPricingBeforeSendPush(PushCSCTransaction pushCSCTransaction) {
        int transactionType = pushCSCTransaction.getCscTransaction().getTransactionType();
        if (!pushCSCTransaction.getControlArea().getSource().equals("TEST") ||
            transactionType != 30 &&
                transactionType != 31)
            return false;

        boolean sendRequestSuccessfully = sendPriceRequest(pushCSCTransaction);

        return sendRequestSuccessfully && transactionType == 31;
    }

    private boolean sendPriceRequest(PushCSCTransaction pushCSCTransaction) {
        try {
            OauthRequest oauthRequest = new OauthRequest(Configuration.Pricing.getOauthScope(), Configuration.Pricing.getOauthClientId(), Configuration.Pricing.getOauthClientSecret());
            Response<OauthResponse> oauthResponse = pricingOauthApi.oauth(oauthRequest, Configuration.Pricing.getOauthPathId()).execute();

            if (!oauthResponse.isSuccessful() || oauthResponse.body() == null) throw new WebApplicationException("");

            Response<Void> response = pricingApi.pricing(pushCSCTransaction, "Bearer " + oauthResponse.body().getAccessToken()).execute();

            return response.isSuccessful();
        } catch (Exception exception) {
            LOGGER.error("Error when trying to send transaction message", exception);
            return false;
        }
    }
}

@ExtendWith(MockitoExtension.class)
public class PricingServiceTest {
    @Test
    public void shouldNotRequirePricingIfRequestFails() throws IOException {
        PricingApi pricingApi = mock();
        PricingOauthApi pricingOauthApi = mock();
        try (MockedStatic<PricingApiFactory> pricingApiFactory = Mockito.mockStatic(PricingApiFactory.class);
             MockedStatic<PricingOauthApiFactory> pricingOauthApiFactory = Mockito.mockStatic(PricingOauthApiFactory.class)) {
            pricingApiFactory.when(PricingApiFactory::create).thenReturn(pricingApi);
            pricingOauthApiFactory.when(PricingOauthApiFactory::create).thenReturn(pricingOauthApi);
            Call<OauthResponse> call = mock(Call.class);
            Mockito.when(pricingOauthApi.oauth(Mockito.any(), Mockito.any())).thenReturn(call);
            Mockito.when(call.execute()).thenThrow(new IOException("Test"));

            PushCSCTransaction transaction = createCsvTransaction("TEST", 31, PRODUCT_CODE, 1312, 311, 1);
            PricingService service = new PricingService();

            boolean result = service.requiresPricingBeforeSendPush(transaction);

            assertFalse(result);
            Mockito.verify(pricingOauthApi).oauth(Mockito.any(), Mockito.any());
            Mockito.verify(pricingApi, Mockito.never()).pricing(Mockito.any(), Mockito.any());
        }
    }

    @Test
    public void shouldRequirePricingIfSucceeding() throws IOException {
        PricingApi pricingApi = mock();
        PricingOauthApi pricingOauthApi = mock();
        try (MockedStatic<PricingApiFactory> pricingApiFactory = Mockito.mockStatic(PricingApiFactory.class);
             MockedStatic<PricingOauthApiFactory> pricingOauthApiFactory = Mockito.mockStatic(PricingOauthApiFactory.class)) {
            pricingApiFactory.when(PricingApiFactory::create).thenReturn(pricingApi);
            Call<Void> call = mock(Call.class);
            Mockito.when(pricingApi.pricing(Mockito.any(), Mockito.any())).thenReturn(call);
            Mockito.when(call.execute()).thenReturn(Response.success(null));

            pricingOauthApiFactory.when(PricingOauthApiFactory::create).thenReturn(pricingOauthApi);
            Call<OauthResponse> callOauth = mock(Call.class);
            Mockito.when(pricingOauthApi.oauth(Mockito.any(), Mockito.any())).thenReturn(callOauth);
            OauthResponse oauthResponse = new OauthResponse();
            Mockito.when(callOauth.execute()).thenReturn(Response.success(oauthResponse));

            PushCSCTransaction transaction = createCsvTransaction("TEST", 31, PRODUCT_CODE, 1312, 311, 1);
            PricingService service = new PricingService();

            boolean result = service.requiresPricingBeforeSendPush(transaction);

            assertTrue(result);
            Mockito.verify(pricingOauthApi).oauth(Mockito.any(), Mockito.any());
            Mockito.verify(pricingApi).pricing(Mockito.any(), Mockito.any());
        }
    }
}

So when I run this code I get now always a true from sendPriceRequest since the mock will never change to throw a exception. What am I forgetting to unmock static methods? I am using Java 8 with mockito version 4.11.0 and junit 5.

Also tried adding:

@BeforeEach
    public void setUp() {
        Mockito.clearAllCaches();
    }

But that didn't work either.


Solution

  • The problem has nothing to do with mocking static methods. Your mockStatic calls are correctly wrapped in try-with-resources blocks.

    The problem is in your PricingService which uses static fields. Static fields pricingOauthApi and pricingApi are initialized only once - when you create the first instance of your service.

    Observe simpler example:

    class FooCollaborator {
    }
    
    class FooService {
        static FooCollaborator fooCollaborator = createFoo();
    
        static FooCollaborator createFoo() {
            System.out.println("static createFoo called");
            return new FooCollaborator();
        }
    
    }
    
    
    public class StaticTest {
        @Test
        void test1() {
            System.out.println("test1");
            FooService fooService = new FooService();
            System.out.println(fooService.fooCollaborator);
        }
    
        @Test
        void test2() {
            System.out.println("test2");
            FooService fooService = new FooService();
            System.out.println(fooService.fooCollaborator);
        }
    }
    

    Output:

    test1
    static createFoo called
    com.so.examples.mockito.FooCollaborator@327514f
    
    test2
    com.so.examples.mockito.FooCollaborator@327514f
    
    • static field is initialized only once
    • the initialization happens when the first instance of your service is created (thus, you have a chance to stub your static methods)

    Typical pattern is not to hold the collaborators of your service in static fields. Even better, pass them as constructor arguments (as an extra benefit, you wont need to mock static factory methods in your test).