I'm working on a Spring Boot project. I'm writing a "Unit Test" code based on "TDD," which is a little bit difficult.
@SpringBootTest loaded all BEANs, which led to longer test times.
So I used the @SpringBootTest's class designation.
I completed the test normally, but I am not sure the difference between using @ContextConfiguration and using @Import.
All three options run normally. I want to know which choice is the best.
@Service
public class CoffeeService {
private final CoffeeRepository coffeeRepository;
public CoffeeService(CoffeeRepository coffeeRepository) {
this.coffeeRepository = coffeeRepository;
}
public String getCoffee(String name){
return coffeeRepository.findByName(name);
}
}
public interface CoffeeRepository {
String findByName(String name);
}
@Repository
public class SimpleCoffeeRepository implements CoffeeRepository {
@Override
public String findByName(String name) {
return "mocha";
}
}
Option 1(SpringBootTest Annotation) - OK
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {CoffeeService.class, SimpleCoffeeRepository.class})
public class CoffeeServiceTest {
@Autowired
private CoffeeService coffeeService;
@Test
public void getCoffeeTest() {
String value = coffeeService.getCoffee("mocha");
assertEquals("mocha", value);
}
}
Option 2 (ContextConfiguration Annoation) - OK
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {SimpleCoffeeRepository.class, CoffeeService.class})
public class CoffeeServiceTest {
@Autowired
private CoffeeService coffeeService;
@Test
public void getCoffeeTest() {
String value = coffeeService.getCoffee("mocha");
assertEquals("mocha", value);
}
}
Option 3 (Import Annoation) - OK
@RunWith(SpringRunner.class)
@Import({SimpleCoffeeRepository.class, CoffeeService.class})
public class CoffeeServiceTest {
@Autowired
private CoffeeService coffeeService;
@Test
public void getCoffeeTest() {
String value = coffeeService.getCoffee("mocha");
assertEquals("mocha", value);
}
like @MarkBramnik says if you're intent is to write a unit test you have to mock other components that uses the specific one you're testing. @SpringBootTest is recomended if you want to write an integration test that simulated the application process. @ContextConfiguration is used when you @Autowired a component in you're unit test and you have to set to the configuration that class, or the class where you created the bean
I think all 3 presented options are bad if your intent is to run a proper unit test. A unit test must be blazing fast, you should be able to run hundreds of those in a second or so (depending on the hardware of course, but you get the idea). So once you say "I start spring for each test" - it's not a unit test anymore. Starting spring for each test is a very expensive operation.
What's interesting is that your code of
CoffeeService
is written in a way that it's perfectly testable: Just use some library like Mockito to mock the repository class and you can test the service logic without any spring at all. You won't need any spring runner, any spring annotations. You'll also see that these tests are running much faster.Now regarding spring test library
You can think about it as a way to test code that requires some interconnections with other components and its problematic to mock everything out. Its a kind of integration test inside the same JVM. All the ways that you've presented run an Application Context and this is a very complicated thing actually under the hood, there are entire sessions on youtube about what really happens during the application context startup - although, beyond the scope of the question, the point is that it takes time to execute the context startup
@SpringBootTest
goes further and tries to mimic the processes added by Spring Boot framework for creating the context: Decides what to scan based on package structures, loads external configurations from predefined locations optionally runs autoconfiguration starters and so on and so forth.Now the application context that might load all the beans in the application can be very big, and for some tests, it's not required. Its usually depends on what is the purpose of the test
For Example, if you test rest controllers (that you've placed all the annotations correctly) probably you don't need to start up DB connections.
All the ways you've presented filter what exactly should be run, what beans to load and to inject into each other.
Usually, these restrictions are applied to "layers" and not to single beans (layers = rest layer, data layer and so forth).
The second and third methods are actually the same, they are different ways to "filter" the application context preserving only the necessary beans.
Update:
Since you've already done the performance comparison of the methods:
Unit test = very fast test, it's purpose is to verify the code you've written (or one of your colleagues of course) So if you run Spring its automatically means a relatively slow test. So to answer your question
No, it cannot, it's an integration test that runs only one class in spring.
Usually, we don't run only one class with Spring Framework. What is the benefit of running it inside the spring container if you only want to test the code of one class (a unit)? Yes, in some cases it can be a couple of classes, but not tens or hundreds.
If you run one class with spring then, in any case, you'll have to mock all its dependencies, the same can be done with mockito...
Now regarding your questions
@SpringBootTest
is relevant only if you have a Spring Boot application. This framework uses Spring under the hood but, in a nutshell, comes with many pre-defined recipes/practices of how to write the "infrastructure" of the application: - configuration management, - package structure, - pluggability - logging - database integration etc.So Spring Boot establishes well-defined processes to deal with all the aforementioned items, and if you want to start the test that will mimic the spring boot application, then you use
@SpringBootTest
annotation. Otherwise (or in case you have only spring driven application and not a spring boot) - don't use it at all.@ContextConfiguration
is an entirely different thing though. It just says what beans would you like to use in Spring driven application (it also works with spring boot)As I said - all the spring test related stuff is for integration testing only, so no, it's a wrong way to be used in unit tests. For unit tests go with something that doesn't use spring at all (like mockito for mocks and a regular junit test without spring runner).