Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@SpringBootTest vs @ContextConfiguration vs @Import in Spring Boot Unit Test

Tags:

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 Annotation) - 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 Annotation) - 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 image 391
sieunkim Avatar asked Jun 22 '19 04:06

sieunkim


People also ask

What is @ContextConfiguration in spring test?

@ContextConfiguration defines class-level metadata that is used to determine how to load and configure an ApplicationContext for integration tests.

What is @SpringBootTest in spring boot?

@SpringBootTest is a primary annotation to create unit and integration tests in Spring Boot applications. The annotation enables additional features such as custom environment properties, different web environment modes, random ports, TestRestTemplate and WebTestClient beans.

What is @SpringBootTest used for?

The @SpringBootTest annotation is useful when we need to bootstrap the entire container. The annotation works by creating the ApplicationContext that will be utilized in our tests. We can use the webEnvironment attribute of @SpringBootTest to configure our runtime environment; we're using WebEnvironment.

Which annotation can be used to run quick unit tests?

The @SpringBootTest annotation can be used to run quick unit tests in Spring Boot.


Video Answer


2 Answers

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.

class MyServiceTest {

    @Test
    public void test_my_service_get_coffee_logic() {
          
           // setup:
           CoffeeRepository repo = Mockito.mock(CoffeeRepository.class);
           Mockito.when(repo.findByName("mocha")).thenReturn("coffeeFound");

           CoffeeService underTest = new CoffeeService(repo);

           // when:
           String actualCoffee  =  underTest.getCoffee("mocha");

           // then:
           assertEquals(actualCoffee, "coffeeFound");
    }
}
 

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

Whether using @ContextConfiguration can be a "Unit Test"

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

@ContextConfiguration vs. @SpringBootTest technical differences.

@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)

Is "Unit Test" the correct way to use @ContextConfiguration? Or not?

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).

like image 113
Mark Bramnik Avatar answered Sep 17 '22 15:09

Mark Bramnik


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 your unit test and you have to set to the configuration that class, or the class where you created the bean

like image 42
Michael Cauduro Avatar answered Sep 18 '22 15:09

Michael Cauduro