Tip of the day #69: Use factories to mock your test
If you’ve been following this series, you probably remember some discussions about Design Patterns. And today’s post follows (a little) that same spirit — by not talking about them =D
So no, we won’t use the Factory Method Design Pattern here. Instead, let’s talk about a common term in tests: the Test Data Factory.
Why use this?
Well, simplifying object creation is an awesome way to help people write more tests. It’s not unusual to hear things like:
“Hey Bruno, to write this unit test I’ll have to mock 10 objects... it’ll take me another day to finish this task.”
Sound familiar? Ever heard it — or even said it? I did!
If you like definitions, here’s one:
A Test Data Factory is a utility class that helps you reuse code and provides predefined objects to make your tests cleaner and more focused.
Let’s take an example. Suppose you have a Car
class:
public class Car {
private String make;
private String model;
private int year;
private String color;
private double price;
... all the other methods here
}
Now, when you start writing tests, you can create a CarTestFactory
to help you:
import java.util.stream.Stream;
import org.junit.jupiter.params.provider.Arguments;
public class CarTestFactory {
public static Car createRedTeslaModelS() {
return new Car("Tesla", "Model S", 2022, "Red", 89999.99);
}
public static Car createBlueToyotaCorolla() {
return new Car("Toyota", "Corolla", 2018, "Blue", 17999.50);
}
public static Car createBlackFordMustang() {
return new Car("Ford", "Mustang", 2020, "Black", 39999.00);
}
public static Stream<Arguments> provideCarsWithExpectedPrices() {
return Stream.of(
Arguments.of(createBlueToyotaCorolla(), 17099.525), // 5% off
Arguments.of(createRedTeslaModelS(), 88199.9902), // 2% off
Arguments.of(createBlackFordMustang(), 39999.00) // 0% off
);
}
}
If you’re wondering why that last method exists — I promise it’ll make sense in a minute.
So, just for demonstration purposes, let’s create a service with a very simple method that applies discounts based on car color:
public class CarService {
public void applyColorDiscount(Car car) {
double discountPercentage = getDiscountByColor(car.getColor());
double discountAmount = car.getPrice() * (discountPercentage / 100.0);
car.setPrice(car.getPrice() - discountAmount);
}
private double getDiscountByColor(String color) {
return switch (color.toLowerCase()) {
case "blue" -> 5.0;
case "red" -> 2.0;
case "black" -> 0.0;
default -> 0.0;
};
}
}
And now let’s test it — using the factory we created earlier:
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.*;
public class CarServiceTest {
private final CarService carService = new CarService();
@ParameterizedTest @MethodSource("com.example.demo.CarTestFactory#provideCarsWithExpectedPrices")
void shouldApplyColorDiscountCorrectly(Car car, double expectedPrice) {
carService.applyColorDiscount(car);
assertEquals(expectedPrice, car.getPrice(), 0.01);
}
}
Extra tip: Notice how I used the fully qualified method name (com.example.demo.CarTestFactory#...
) — this is required when the method is not in the same class.
Because color is important in my domain, I like to create objects like createBlueCorolla()
or createRedTeslaModelS()
— it makes the tests easier to read and maintain.
You can keep improving the factory too. One thing I like to do is add generic methods like withColor()
or from()
:
public static createCorollaWithColor(String color){
return new Car("Toyota", "Corolla", randomYear, color, randomPrice);
}
But here’s something to remember: generics are not enough. Creating full-context, intention-revealing objects helps other developers better understand your tests — and your system.
And that’s it!
Do you use something like this? Want help getting started? Let me know!
Happy coding!