Explain Codes LogoExplain Codes Logo

Mocking static methods with Mockito

java
mockito
testing
static-methods
Alex KataevbyAlex Kataev·Oct 3, 2024
TLDR

Quickly simulate static methods in Java with Mockito through the Mockito.mockStatic function introduced from version 3.4.0+. Enclose your test body inside a try-with-resources sphere such that static mocks are defined and automatically discarded.

try (MockedStatic<UtilityClass> mocked = Mockito.mockStatic(UtilityClass.class)) { mocked.when(UtilityClass::staticMethod) // utility belt unlocking static methods .thenReturn("Mock"); // "I'm Batman, this is my Mock" // Assertion with "Mock" returned from staticMethod }

This restricts static mocks to this area only, offering pure tests free from residual effects.

The nitty-gritty of mocking static methods

Before we nudge forward, it's crucial to clear up when and why we want to mock static methods. Under the parasol of perfect world scenarios, we design our classes to be easily testable, avoiding static methods due to their inherent un-mockability. However, the rain often leaks through via legacy systems or unavoidable designs, and we find outselves needing to mock the un-mockable. Let's get wet.

Try-with-resources: Limiting mock's playtime

With Mockito 3.4.0+, we can box our static mocks thanks to Java's try-with-resources. This ensures that the mocks are only at play during their assigned time, and cleaned up nicely afterwards:

try (MockedStatic<UmbrellaFactory> mocked = Mockito.mockStatic(UmbrellaFactory.class)) { mocked.when(UmbrellaFactory::createUmbrella) // Umbrellas at factory price .thenReturn("Red Umbrella"); // Today's forecast: Red with a chance of snowflakes // Assertion with "Red Umbrella" returned from createUmbrella() // It's raining assertions, hallelujah! }

Handling exceptions: The roar of thunder

Testing isn't always sunny skies and passing tests. For those stormy days translating to exceptions, Mockito comes with a good raincoat:

try (MockedStatic<Weather> theMock = Mockito.mockStatic(Weather.class)) { theMock.when(Weather::getForecast) // Predicting some heavy testing .thenThrow(new RuntimeException("Storm Warning")); // Who ordered the thunderstorm? assertThrows(RuntimeException.class, Weather::getForecast); // This test was thunderstruck }

Embracing legacy code: Wrapper classes to the rescue!

Older codebases may not play well with our modern mocking practices, and may require a gentler approach. Enter the wrapper classes – a softer blanket around the frosty static instances:

public class WeatherWrapper { public String getForecastWrapper() { return Weather.getForecast(); // Please let it be sunny } } // Now, mock the wrapper instead of the static WeatherWrapper mockWrapper = mock(WeatherWrapper.class); when(mockWrapper.getForecastWrapper()).thenReturn("Sunny"); // Always sunny in the test-adelphia

Escaping your comfort zone: The uncharted territory

While we spent our day wading through the puddles of easier mocking, it's time to take on the floods– the tougher ones.

Final system classes: The un-mockables

What about testing interactions with final or system classes, like java.lang.System? Quite a monsoon, right? But with the right gear, we're all set:

try (MockedStatic<System> mocked = Mockito.mockStatic(System.class)) { mocked.when(() -> System.currentTimeMillis()) // The tic-toc of testing .thenReturn(123456789L); // Time travel enabled assertEquals(123456789L, System.currentTimeMillis()); // The flux capacitor works! }

Working with older versions: PowerMockito to the rescue!

Back in the day, without the luxury of Mockito 3.4.0+, we had to pull a MacGyver and resort to our Swiss Army Knife, PowerMockito:

@RunWith(PowerMockRunner.class) @PrepareForTest({UtilityClass.class}) public class MyTest { @Test public void testStaticMethodWithPowerMockito() throws Exception { PowerMockito.mockStatic(UtilityClass.class); BDDMockito.given(UtilityClass.staticMethod()) // Giving some static love .willReturn("Mock"); // Returned with some mock affection assertEquals("Mock", UtilityClass.staticMethod()); // Love affirmed! PowerMockito.verifyStatic(UtilityClass.class); // Been there, mocked that } }

The aftermath: Clean up and review

Following the methods above, we can now pilot our way through the torrential downpour that static methods seemed at first. To ensure your code stays dry, remember these rules:

  • Always limit the scope of your mock's playtime with try-with-resources.
  • Stay prepared for exceptions with the assertThrows mechanism.
  • Embrace legacy code with wrapper classes and Mockito.

By keeping these principles in mind, you'll become the Beethoven of testing – the maestro of static mocking!