We are arguing about this approach with my colleagues. They say to use SpringRunner only on integration or functional levels.
The question is what pros and cons of using it in level below?
For example I have simple bean:
public class RewardDurationCalculator {
private Clock clock;
public OptionalLong calculate(DurationType durationType, List<Pass> passes) {
long now = Instant.now(clock).getEpochSecond();
switch (durationType) {
case FULL_PASS:
return getCurrentPassDuration(passes, now);
case TILL_THE_END_OF_THE_CURRENT_ACTIVE_PASS:
return getTimeInCurrentPassLeft(passes, now);
}
return OptionalLong.empty();
}
private OptionalLong getCurrentPassDuration(List<Pass> passes, long now) {
return passes.stream()
.filter(currentPass(now))
.mapToLong(Pass::getDuration)
.findFirst();
}
private OptionalLong getTimeInCurrentPassLeft(List<Pass> passes, long now) {
return passes.stream()
.filter(currentPass(now))
.mapToLong(pass -> getEndTs(pass) - now)
.findFirst();
}
private Predicate<Pass> currentPass(long now) {
return pass -> pass.getStartTs() >= now && now <= getEndTs(pass);
}
private long getEndTs(Pass pass) {
return pass.getStartTs() + pass.getDuration();
}
}
that is doing some calculation logic. For it I have also spring config:
@Configuration
public class RewardDurationCalculatorConfiguration {
@Bean
public RewardDurationCalculator rewardDurationCalculator(Clock clock) {
return new RewardDurationCalculator(clock);
}
}
So why can't I write unit test for it like this:
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = RewardDurationCalculatorConfiguration.class)
public class RewardDurationCalculatorTest {
@MockBean
private Clock clock;
@Autowired
private RewardDurationCalculator rewardDurationCalculator;
@Test
public void testCalculateCurrentPassDurationShouldBeReturnedIfPassWasCreatedRightNow() {
rewardDurationCalculator.calculate(DurationType.FULL_PASS, Collections.emptyList());
}
}
What cons I can face with using such approach?
I tend to agree with your colleagues.
Unit tests should only test small units of code, typically a single class. They should exercise only the unit under test without executing any code from dependencies of the unit.
One reason for this is that unit tests should execute as fast as possible. In Test Driven Development (TDD), you want to be able to run the suite, or a at least a relevant subset of it, very often, after every small change you make, to verify that you didn't break any existing behavior. This is only feasible if the feedback from the tests is instantaneous. If you have to wait for the test results too long, you'll run them less often, which means the feedback loop gets longer.
In smaller Spring projects the Spring context loads quiet fast, so you might think there's not that much of a difference, but if you run the tests very often, even a short delay adds up. In large and older code bases it can take a while to ramp up the Spring context, and at some point it gets so slow that you don't want to run the whole test suite more than once a day, which considerably harms your ability to practice TDD.
Another dimension of sticking to testing small units of code is that it forces you to structure your code into highly decoupled (i. e. testable) units. If you can't write unit tests for a class that exercises only that class and nothing else, it's probably a hint that there's an opportunity to improve the design of the code.
To summarize, when you ramp up the whole Spring context for a test, by this definition, it is not a unit test anymore, but an integration test, because you are also testing the whole Spring configuration, bootstrapping, autowiring etc, i. e. the integration of your classes into a Spring application.
@SpringRunner should be used if you class is using any bean dependencies, if you class is independent one, without any application context dependencies, better not to use @SpringRunner. If i see you class 'RewardDurationCalculator' it doesn't use any dependencies, it should not use @SpringRunner and even for matter of fact Mocks...
For further reading:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With