2

I have a controller

@RestController
public class Create {

    @Autowired
    private ComponentThatDoesSomething something;

    @RequestMapping("/greeting")
    public String call() {
        something.updateCounter();
        return "Hello World " + something.getCounter();
    }

}

I have a component for that controller

@Component
public class ComponentThatDoesSomething {
    private int counter = 0;

    public void updateCounter () {
        counter++;
    }

    public int getCounter() {
        return counter;
    }
}

I also have a test for my controller.

@RunWith(SpringRunner.class)
@SpringBootTest
public class ForumsApplicationTests {

    @Test
    public void contextLoads() {
        Create subject = new Create();
        subject.call();
        subject.call();
        assertEquals(subject.call(), "Hello World 2");
    }

}

The test fails when the controller calls something.updateCounter(). I get a NullPointerException. While I understand it's possible to add @Autowired to a constructor I would like to know if there is anyway to do this with an @Autowired field. How do I make sure the @Autowired field annotation works in my test?

Stewart
  • 3,023
  • 2
  • 24
  • 40

3 Answers3

5

Spring doesn't auto wire your component cause you instantiate your Controller with new not with Spring, so Component is not instatntiated

The SpringMockMvc test check it correct:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class CreateTest {
    @Autowired
    private WebApplicationContext context;

    private MockMvc mvc;

    @Before
    public void setup() {
        mvc = MockMvcBuilders
                .webAppContextSetup(context)
                .build();
    }

    @Test
    public void testCall() throws Exception {
        //increment first time
        this.mvc.perform(get("/greeting"))
                .andExpect(status().isOk());
        //increment secont time and get response to check
        String contentAsString = this.mvc.perform(get("/greeting"))
                .andExpect(status().isOk()).andReturn()
                .getResponse().getContentAsString();
        assertEquals("Hello World 2", contentAsString);
    }
}
Sergii Getman
  • 3,845
  • 5
  • 34
  • 50
2

The @Autowired class can be easily mocked and tested with MockitoJUnitRunner with the correct annotations.

With this you can do whatever you need to do with the mock object for the unit test.

Here is a quick example that will test the Create method call with mocked data from ComponentThatDoesSomething.

@RunWith(MockitoJUnitRunner.class)
public class CreateTest {

    @InjectMocks
    Create create;
    @Mock
    ComponentThatDoesSomething componentThatDoesSomething;

    @Test
    public void testCallWithCounterOf4() {

        when(componentThatDoesSomething.getCounter()).thenReturn(4);
        String result = create.call();
        assertEquals("Hello World 4", result);
    }
}
Dave
  • 21
  • 1
1

Use Mockito and inject a mock that you create. I would prefer constructor injection:

@RestController
public class Create {

    private ComponentThatDoesSomething something;

    @Autowired
    public Create(ComponentThatDoesSomething c) {
        this.something = c;
    }
}

Don't use Spring in your Junit tests.

public CreateTest {

    private Create create;

    @Before
    public void setUp() {
        ComponentThatDoesSomething c = Mockito.mock(ComponentThatDoesSomething .class);
        this.create = new Create(c);
    } 
}
duffymo
  • 305,152
  • 44
  • 369
  • 561
  • Thanks. Great answer! Is there no other way to do this with out creating an auto wired constructor? I have existing code that I was hoping to write a test for with out refactoring too much. – Stewart Oct 06 '16 at 09:44
  • 1
    How hard is it to add a constructor? That's not refactoring, just work. Do it. – duffymo Oct 06 '16 at 09:45
  • 1
    This whole question was designed to find out if tests can be written with an autowired field rather than an autowired constructor. If this can't be done can you change your answer. If it can, can you post another code example and I will accept. Thanks again for your time. – Stewart Oct 06 '16 at 09:49
  • 1
    My answer won't change. I am recommending that you not autowire dependencies and not use Spring in your unit tests. I'm basing that on personal experience after years of using Junit and Spring. I don't care what you accept. You can follow my suggestion or not. – duffymo Oct 06 '16 at 09:57
  • Calling "new" means Spring is out of the picture for the object you created. It is no longer responsible for autowiring any bean that is not under its control. That's why you get the NPE. I prefer not using Spring because it forces me to bring in all those dependencies. Using mocks, injected by me, keeps my unit test for a single class from turning into an integration test with my entire object graph. – duffymo Oct 06 '16 at 12:24