9

I have a very simple rest controller:

@RestController
public class MyController {
    @Autowired
    public Logger logger;

The logger dependency gets injected via the following configuration:

@Configuration
public class MyConfig {
    @Bean
    public Logger logger() {
        return LoggerFactory.getLogger(MyController.class);
    }

If I run the Spring application that contains the controller then everything works fine. However, I cannot manage to achieve this dependency injection when running my unit tests. In this case I have the following test configuration:

@Configuration
@Profile("test")
public class MyTestConfig {
    @Bean
    public Logger logger() {
        return LoggerFactory.getLogger(MyCOntroller.class);
    }

And this is the relevant part of my unit tests code:

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(classes = MyTestConfig.class)
@ActiveProfiles("test")
public class MyContollerTest {

However the logger object does not get "autowired" in MyController (note that I do not want to mock the logger object), which results in a null pointer reference.

What am I missing?

Damian Nadales
  • 4,907
  • 1
  • 21
  • 34

2 Answers2

13

A unit test shouldn't use any Spring configuration. You should simply instantiate your component, and inject dependencies (usually fake ones) manually.

You used field injection, which makes it a bit harder. With constructor injection, all you would need to do is

Logger logger = LoggerFactory.getLogger(MyController.class);
MyController controller = new MyController(logger);

Mockito can help injecting fake dependencies for you, though, even when using field injection, thanks to the @Mock, @Spy and @InjectMocks annotations:

@Spy
private Logger logger = LoggerFactory.getLogger(MyController.class);

@InjectMocks
private MyController controller;

@Before
public void prepare() {
    MockitoAnnotations.initMocks(this);
}

That said, if I'm not mistaken, you're not using @RunWith(SpringJUnit4ClassRunner.class), so your test runner doesn't know anything about Spring, and thus doesn't create or use any Spring configuration.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • 2
    Yeah, the `SpringJUnit4ClassRunner` is the real culprit here, imo. This is more of an integration than a unit test. – Sotirios Delimanolis Dec 11 '15 at 20:07
  • 1
    It seems I took the wrong approach when writing unit tests with Spring then. Using field injection and leaving Spring out in the unit tests seems to be the way to go here. Thanks for the explanation. – Damian Nadales Dec 14 '15 at 07:51
0

I agree that a unit test shouldn't ideally include spring configuration, however; if you still wish to perform a unit test with Spring managed bean(s) you can use SpringExtension's parameter resolver.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/junit/jupiter/SpringExtension.html

https://rieckpil.de/what-the-heck-is-the-springextension-used-for/

    @RunWith(MockitoJUnitRunner.class)
    @ContextConfiguration(classes = MyTestConfig.class)
    @ActiveProfiles("test")
    // New addition
    @ExtendWith(SpringExtension.class)
        @Import(
            value = {
        MyController.class,Myconfig.class, MyTestConfig.class
    }

    public class MyContollerTest {

Note, SpringExtension is used automatically when the @SpringBootTest annotation is used, however; for Controller tests like in this example, it has to be added explicitly.

rdChris
  • 96
  • 4