1
votes

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?

3
Inject a mock without Spring.duffymo
Can you post an answer with a code example?Stewart

3 Answers

5
votes

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);
    }
}
1
votes

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);
    } 
}
1
votes

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);
    }
}