By Ashley Waldron
All of the source code for this series can be found in this repository. Specifically, the code used throughout this item can be found here and its corresponding test classes found here.
Traditionally utility classes were written as final classes with a bunch of static utility methods. However there can be a drawback with this approach that can be alleviated by turning these classes into component dependencies when using a dependency injection framework like Spring. Static utility classes are useful when code needs to carry out some behavior that doesn’t rely on state and which can be useful to multiple classes. And if the method/class doesn’t rely on any external dependency injection then making it static makes sense. However, what this also means is that each class which uses that static method, and all of their unit test classes, are coupled to its logical implementation. To see what I mean consider the following static utility method [ColorPickerUtils in package item9.a]:
package com.effective.unit.tests.service.item9;
import java.util.HashMap;
import java.util.Map;
public final class ColorPickerUtils {
public static final String BLANK_VALUE = "blank";
private static final Map<String, String> COLORS = new HashMap<>();
static {
COLORS.put("red", "#9f1111");
COLORS.put("green", "#39ff141");
COLORS.put("blue", "#0000ff");
COLORS.put("purple", "#0000ff");
}
public static String pick(String colorName) {
return COLORS.getOrDefault(colorName, BLANK_VALUE);
}
}And the following class which uses it [ColorService in package item9.a]:
package com.effective.unit.tests.service.item9;
import static com.effective.unit.tests.service.item9.ColorPickerUtils.BLANK_VALUE;
public class ColorService {
public MyShape draw(int height, int width, String color) {
return drawRectangle(height, width, ColorPickerUtils.pick(color));
}
private MyShape drawRectangle(int height, int width, String color) {
MyShape myShape;
if(color.equals(BLANK_VALUE)) {
myShape = MyShape.EMPTY;
} else {
myShape = new MyShape(height, width, color);
}
return myShape;
}
}Then, if we write unit tests for the ColorService class they will look something like [ColorServiceTest in package item9.a]:
package com.effective.unit.tests.service.item9.a;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.MatcherAssert.assertThat;
public class ColorServiceTest {
private static final int TEST_HEIGHT = 2;
private static final int TEST_WIDTH = 1;
private static final String TEST_VALID_SOURCE_COLOR = "green";
private static final String TEST_INVALID_SOURCE_COLOR = "orange";
private static final String TEST_VALID_HEX_COLOR = "#39ff141";
private final ColorService colorService = new ColorService();
@Test
void drawSuccess() {
MyShape result = colorService.draw(TEST_HEIGHT, TEST_WIDTH, TEST_VALID_SOURCE_COLOR);
assertThat(result.getHeight(), is(TEST_HEIGHT));
assertThat(result.getWidth(), is(TEST_WIDTH));
assertThat(result.getColor(), is(TEST_VALID_HEX_COLOR));
}
@Test
void drawOrangeShouldBeBlank() {
MyShape result = colorService.draw(TEST_HEIGHT, TEST_WIDTH, TEST_INVALID_SOURCE_COLOR);
assertThat(result, is(sameInstance(MyShape.EMPTY)));
}
}You’ll notice that the unit test has to “know” how the ColorPickerUtils.pick() method works. From the unit test’s point of view the method is equivalent to a private method inside the ColorService class. In order for the unit test to drive the different scenarios (the generation of the possible different MyShape objects) it has to know exactly what parameter value needs to get sent into the ColorPickerUtils.pick() method to get back the value that it needs. The test also has to trust that it sent out the correct parameters because it has no real way of knowing. Today it can assume that ColorPickerUtils.pick() returns ‘blank‘ for the color yellow but that might not be true in the future. If the behavior of the color ColorPickerUtils.pick() method changes it can change the behavior of the code and cause these tests to fail. It might be tempting to think that if a code change somewhere else breaks a class under test then it should fail the test but these are unit tests, not integration tests. Each unit test should really only test the class that it’s testing. It’s not ideal for it to also have a direct logical dependency on other classes that it doesn’t have a relationship with.
Copied test logic
There’s also another drawback. Imagine that the ColorPickerUtils.pick() method is used throughout the application in various places. That means that all of the unit tests for those classes will also have the same logic copy and pasted in each one (sending in the correct colors for different scenarios that they may need to drive).
Unfindable bugs
You can also introduce bugs in the code that you can’t find. As a fairly silly example, if a developer mistakenly updated the code to change blue to purple by doing the following silliness:
public MyShape draw(int height, int width, String color) {
if("blue".equals(color)) {
color = "purple";
}
return drawRectangle(height, width, ColorPickerUtils.pick(color));
}The tests still pass even though this wasn’t the intention (try it)! You can argue that since the resulting behavior is the same then it doesn’t really affect the way our code works. But this is dangerous and at the end of the day doesn’t uncover an unintended piece of logic in our code (which can have an unnecessary impact on performance but more importantly might cause behavioral problems down the line). Also, it becomes more dangerous if we modified a field on a request object (just like we did in Item 4 [Item4UserService.create()]). There’s no way to verify what value we gave the external class exactly and unit tests don’t normally assert that the test request object wasn’t modified during the test run. We would have modified the request object which may be something that gets passed to other methods after this one back in the calling class and now it’s been incorrectly changed. None of the unit tests in your test suite will be able to catch this. Unless you’re checking that the request object sent in by the test hasn’t been modified which is probably unrealistically verbose, and a convention you would then have to introduce into every test to be consistent.
Add to the above points the fact that this code example is a fairly silly trivial example. Utility methods could contain quite verbose logic and when that’s true the unit test setup code can become quite convoluted, hard to follow and even more brittle.
Converting classes with static methods into components
An easy way to get around this is to change ColorPickerUtils to be a normal component that’s injected by the dependency injection framework and make the pick() method non-static. Once you do that you can just mock it and setup responses as normal and carry out the relevant assertions. Doing this allows you to completely remove the coupling between the ColorServiceTest unit test class and the ColorPickerUtils class. It also allows you to assert the things that you couldn’t before.
So your classes would then look like this [ColorPickerUtils in package item9.b]:
package com.effective.unit.tests.service.item9.b;
import java.util.HashMap;
import java.util.Map;
public class ColorPickerUtils {
public static final String BLANK_VALUE = "blank";
private static final Map<String, String> COLORS = new HashMap<>();
static {
COLORS.put("red", "#9f1111");
COLORS.put("green", "#39ff141");
COLORS.put("blue", "#0000ff");
COLORS.put("purple", "#0000ff");
}
public String pick(String colorName) {
return COLORS.getOrDefault(colorName, BLANK_VALUE);
}
}[ColorService in package item9.b]
package com.effective.unit.tests.service.item9.b;
import static com.effective.unit.tests.service.item9.a.ColorPickerUtils.BLANK_VALUE;
public class ColorService {
private ColorPickerUtils colorPickerUtils;
public MyShape draw(int height, int width, String color) {
return drawRectangle(height, width, colorPickerUtils.pick(color));
}
private MyShape drawRectangle(int height, int width, String color) {
MyShape myShape;
if(color.equals(BLANK_VALUE)) {
myShape = MyShape.EMPTY;
} else {
myShape = new MyShape(height, width, color);
}
return myShape;
}
}[ColorServiceTest in package item9.b]
package com.effective.unit.tests.service.item9.b;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.BDDMockito.given;
@ExtendWith(MockitoExtension.class)
public class ColorServiceTest {
private static final int TEST_HEIGHT = 2;
private static final int TEST_WIDTH = 1;
private static final String TEST_SOURCE_COLOR = "marine blue";
private static final String TEST_HEX_COLOR = "#46dd202";
private static final String TEST_DEFAULT_HEX_COLOR = "blank";
@InjectMocks
private final ColorService colorService = new ColorService();
@Mock
private ColorPickerUtils colorPickerUtils;
@Test
void drawSuccess() {
given(colorPickerUtils.pick(TEST_SOURCE_COLOR)).willReturn(TEST_HEX_COLOR);
MyShape result = colorService.draw(TEST_HEIGHT, TEST_WIDTH, TEST_SOURCE_COLOR);
assertThat(result.getHeight(), is(TEST_HEIGHT));
assertThat(result.getWidth(), is(TEST_WIDTH));
assertThat(result.getColor(), is(TEST_HEX_COLOR));
}
@Test
void drawOrangeShouldBeBlank() {
given(colorPickerUtils.pick(TEST_SOURCE_COLOR)).willReturn(TEST_DEFAULT_HEX_COLOR);
MyShape result = colorService.draw(TEST_HEIGHT, TEST_WIDTH, TEST_SOURCE_COLOR);
assertThat(result, is(sameInstance(MyShape.EMPTY)));
}
}Note: You’ll have noticed that we changed the source color to “marine blue” which isn’t even supported by the ColorPickerUtils class, plus we’ve also told it to return a hex color of ‘#46d202‘ which also is nowhere to be found in the ColorPickerUtils class. This is because we’ve broken the coupling between the test class and the actual ColorPickerUtils class so we no longer care here about how it really works. The only value that we actually couple our tests to is the value ‘blank‘ that’s returned for unknown colors but that’s only because our ColorService class also knows about that value, because it has logic specifically to deal with it. And so our tests must exercise that particular logic. The point of this note is remind you to try and avoid setting up your mocks with data that corresponds to how the real external class works. There’s nothing inherently wrong with doing that but when you force yourself to not care about it, it allows you to focus on the only thing that actually matters which is how your class is behaving. So my advice is to try and avoid it, except in the scenarios where the class you’re testing deals with some specific information that the external class sends back (like the logic for dealing with the color ‘blank‘ getting returned). Your unit tests should only know the same amount of information about the external classes behavior that the class it’s testing knows.
Static mocking for 3rd party classes
Unfortunately, the above solution isn’t always possible. There are many times when application code has to use static method calls that belong to an external dependency that they do not own and have no control over. It’s not possible to modify these classes directly so a better option if you want to accomplish the above involves 2 main changes to your approach (either of which can be used or even a combination of both together would generally be better).
Create a Delegate class for the 3rd party class
The first option involves creating a delegate class for the static method. Then call that delegate from your application code rather than calling the external static method directly. So to do this we can create the following class [ColorPickerDelegate in package item9.c]:
package com.effective.unit.tests.service.item9.c;
public class ColorPickerDelegate {
public String pick(String colorName) {
return ColorPickerUtils.pick(colorName);
}
}We can then inject this delegate as a dependency of our ColorService class like any other dependency [ColorService in package item9.c]:
package com.effective.unit.tests.service.item9.c;
import static com.effective.unit.tests.service.item9.c.ColorPickerUtils.BLANK_VALUE;
public class ColorService {
private ColorPickerDelegate colorPickerDelegate;
public MyShape draw(int height, int width, String color) {
return drawRectangle(height, width, colorPickerDelegate.pick(color));
}
private MyShape drawRectangle(int height, int width, String color) {
MyShape myShape;
if(color.equals(BLANK_VALUE)) {
myShape = MyShape.EMPTY;
} else {
myShape = new MyShape(height, width, color);
}
return myShape;
}
}Then we can update our unit tests to mock the ColorPickerDelegate class as normal and change our tests to this [ColorServiceTest in package item9.c]:
package com.effective.unit.tests.service.item9.c;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.BDDMockito.given;
@ExtendWith(MockitoExtension.class)
public class ColorServiceTest {
private static final int TEST_HEIGHT = 2;
private static final int TEST_WIDTH = 1;
private static final String TEST_SOURCE_COLOR = "marine blue";
private static final String TEST_HEX_COLOR = "#46dd202";
private static final String TEST_DEFAULT_HEX_COLOR = "blank";
@InjectMocks
private final ColorService colorService = new ColorService();
@Mock
private ColorPickerDelegate colorPickerDelegate;
@Test
void drawSuccess() {
given(colorPickerDelegate.pick(TEST_SOURCE_COLOR)).willReturn(TEST_HEX_COLOR);
MyShape result = colorService.draw(TEST_HEIGHT, TEST_WIDTH, TEST_SOURCE_COLOR);
assertThat(result.getHeight(), is(TEST_HEIGHT));
assertThat(result.getWidth(), is(TEST_WIDTH));
assertThat(result.getColor(), is(TEST_HEX_COLOR));
}
@Test
void drawOrangeShouldBeBlank() {
given(colorPickerDelegate.pick(TEST_SOURCE_COLOR)).willReturn(TEST_DEFAULT_HEX_COLOR);
MyShape result = colorService.draw(TEST_HEIGHT, TEST_WIDTH, TEST_SOURCE_COLOR);
assertThat(result, is(sameInstance(MyShape.EMPTY)));
}
}Finally, we also need to create unit tests for our new ColorPickerDelegate class [ColorPickerDelegateTest in package item9.c]:
package com.effective.unit.tests.service.item9.c;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class ColorPickerDelegateTest {
private static final String TEST_SOURCE_COLOR = "green";
private static final String TEST_EXPECTED_HEX_COLOR = "#39ff141";
private final ColorPickerDelegate colorPickerDelegate = new ColorPickerDelegate();
@Test
void pick() {
String result = colorPickerDelegate.pick(TEST_SOURCE_COLOR);
assertThat(result, is(TEST_EXPECTED_HEX_COLOR));
}
}This approach can be good practice anyway, since the reason we’re doing this is because in this scenario, the ColorPickerUtils class is an external dependency that we can’t change. Hiding it behind an delegate/abstraction and using that class everywhere allows us to swap out dependencies later without changing other code, or even just easily add our own custom behavior for certain colors. If you do choose to do this then you’ll notice that apart from decoupling the unit tests from the ColorPickerUtils class and making unit test code simpler and more maintainable, this change kicks some of the can down the road a bit. We still have to create unit tests for our new delegate class and we have the same problem there where that unit test code is coupled to the ColorPickerUtils class just like our initial problem. However at least this coupling is only in a single unit test class with less methods so that’s at least easier to maintain. We can go one step further though if we want and follow this up with a second approach.
Using Static Mocking
The second approach involves mocking the static method and can either be done in each unit test class which needs to do that or it can be done in conjunction with the first delegate centric approach outlined above (in that case we would only need to do it in the ColorPickerDelegateTest class and nowhere else). But it’s up to the developer which way they think makes more sense for their code base. Sometimes an external static class like ColorPickerUtils can have a ton of methods and constantly adds more methods over time. That means that if your application will use those methods, the delegate will have to be maintained by adding a method for each one your code uses. If your code is only really going to use a few of those methods then creating the delegate might be more effort than it’s worth.
For example, if we didn’t want to create the ColorPickerDelegate class and simply wanted to directly mock the static method in our unit tests we would update our tests to this [ColorServiceTest in package item9.d]:
package com.effective.unit.tests.service.item9.d;
import com.effective.unit.tests.service.item9.a.ColorPickerUtils;
import com.effective.unit.tests.service.item9.a.ColorService;
import com.effective.unit.tests.service.item9.a.MyShape;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.MatcherAssert.assertThat;
public class ColorServiceTest {
private static final int TEST_HEIGHT = 2;
private static final int TEST_WIDTH = 1;
private static final String TEST_SOURCE_COLOR = "marine blue";
private static final String TEST_HEX_COLOR = "#46dd202";
private static final String TEST_DEFAULT_HEX_COLOR = "blank";
private final ColorService colorService = new ColorService();
@Test
void drawSuccess() {
try (MockedStatic<ColorPickerUtils> colorPickerUtils = Mockito.mockStatic(ColorPickerUtils.class)) {
colorPickerUtils.when(() -> ColorPickerUtils.pick(TEST_SOURCE_COLOR))
.thenReturn(TEST_HEX_COLOR);
MyShape result = colorService.draw(TEST_HEIGHT, TEST_WIDTH, TEST_SOURCE_COLOR);
assertThat(result.getHeight(), is(TEST_HEIGHT));
assertThat(result.getWidth(), is(TEST_WIDTH));
assertThat(result.getColor(), is(TEST_HEX_COLOR));
}
}
@Test
void drawOrangeShouldBeBlank() {
try (MockedStatic<ColorPickerUtils> colorPickerUtils = Mockito.mockStatic(ColorPickerUtils.class)) {
colorPickerUtils.when(() -> ColorPickerUtils.pick(TEST_SOURCE_COLOR))
.thenReturn(TEST_DEFAULT_HEX_COLOR);
MyShape result = colorService.draw(TEST_HEIGHT, TEST_WIDTH, TEST_SOURCE_COLOR);
assertThat(result, is(sameInstance(MyShape.EMPTY)));
}
}
}Notes:
- Notice that the entire test is packed inside the try (with resources) block? This is essential for this to work correctly. Just be aware of this because quite a few online examples have asserts both inside and after this block. But they only do this to show that the static mocking is only active inside the try block and reverts to normal behavior afterwards (calling the real static method). Once the test code hits the next line after the try block, any call to
ColorPickerUtils.pick()would call the real method. Those examples are just to highlight that. - This static mocking functionality was only introduced in Mockito 3.4.0 so many code bases don’t use this approach as they haven’t updated tests to use this feature. This may also be true of other mocking frameworks which you may use.
- When debugging these tests it can be bit confusing. If you stick a breakpoint in the
ColorPickerUtils.pick()method you’ll see it actually gets hit twice: Once, on initialization of the mock setup and again when the actual call is made by the code. The second of which is a bit surprising because you would think like normal mocks, it’s not calling the real method. So why would a breakpoint stop in the actual method if it’s the mocked version that’s called? This is just down to the way the static mocking is performed which is none of our concern (for now anyway).
If we choose to do this static mocking in combination with the delegate class approach we would simply change the unit test class for ColorPickerDelegate to [ColorPickerDelegateTest in package item9.e]:
package com.effective.unit.tests.service.item9.e;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@ExtendWith(MockitoExtension.class)
public class ColorPickerDelegateTest {
private static final String TEST_SOURCE_COLOR = "marine blue";
private static final String TEST_HEX_COLOR = "#46dd202";
@InjectMocks
private ColorPickerDelegate colorPickerDelegate;
@Test
void pick() {
try (MockedStatic<ColorPickerUtils> colorPickerUtils = Mockito.mockStatic(ColorPickerUtils.class)) {
colorPickerUtils.when(() -> ColorPickerUtils.pick(TEST_SOURCE_COLOR))
.thenReturn(TEST_HEX_COLOR);
String result = colorPickerDelegate.pick(TEST_SOURCE_COLOR);
assertThat(result, is(TEST_HEX_COLOR));
}
}
}Several Options to choose from
So in summary there are 5 potential ways you can deal with static methods:
- Don’t do anything. Leave all the relevant unit test classes coupled to the static method. Just as the code was at the start of this item (source code package item9.a)
- If the class belongs to your codebase refactor it into a (non-static) dependency injected component and simply mock it (source code package item9.b)
- If the class does not belong to your codebase, wrap it in a delegate class and mock that everywhere (source code package item9.c)
- If the class does not belong to your codebase, use static mocking to mock the calls to the static method in all of your unit test classes (source code package item9.d)
- Do a combination of 3 and 4 (source code package item9.e)
- Option 1 has no real upsides to it so is the least useful approach.
- Option 2 is probably the best if it’s possible.
- Option 3 is a good choice (if option 2 isn’t possible) to reduce the unit test coupling to the static method down to a single unit test class. Especially if the external class doesn’t have many methods that your code uses.
- Option 4 is an alternative to option 3 and might be useful if the external class has many methods that are used and might grow over time.
- Option 5 is good if the external class doesn’t have many methods and you want to reduce all coupling to it from your unit tests.
Note: The only reason I can think of to go with option 3 instead of option 5 is if you want the delegate class unit tests to actually be coupled to the external static method in case it changes behavior in a future version and you want an early warning system but you could extend that argument to any external dependency and it bleeds over into integration testing.