10

Hi All

I’m trying to execute a Junit test in a Spring boot application, the Junit should test some CRUD operations, I’m using Spring Repositories specifically JpaRepository.

The Repository calss:

package au.com.bla.bla.bla.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import au.com.bla.bla.bla.entity.Todo;

public interface TodoRepository extends JpaRepository<Todo, Integer> {

}

TodoController class

package au.com.bla.bla.bla.controller;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import au.com.bla.bla.bla.entity.Todo;
import au.com.bla.bla.bla.repository.TodoRepository;


import java.util.List;
import java.util.Map;


@RestController
@CrossOrigin
public class TodoController
{
    static final String TEXT = "text";

    @Autowired
    private TodoRepository todoRepository;

...
    @RequestMapping(path = "/todo", method = RequestMethod.POST)
    public Todo create(@RequestBody Map<String, Object> body)
    {
        Todo todo = new Todo();
        todo.setCompleted(Boolean.FALSE);
        todo.setText(body.get(TEXT).toString());
        todoRepository.save(todo);
        return todo;
    }
...

The JUnit:

package au.com.bla.bla.bla.controller;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import au.com.bla.bla.bla.repository.TodoRepository;

@WebMvcTest(TodoController.class)
@RunWith(SpringRunner.class)
public class TodoControllerTest {

  @Autowired
  private MockMvc mvc;

  @Autowired
  private TodoController subject;

  @Before
 public void setUp() {

  }

  @Test
  public void testCreate() throws Exception {

    String json = "{\"text\":\"a new todo\"}";

    mvc.perform(post("/todo").content(json)
                             .contentType(APPLICATION_JSON)
                             .accept(APPLICATION_JSON))
       .andExpect(status().isOk())
       .andExpect(jsonPath("$.id").value(3))
       .andExpect(jsonPath("$.text").value("a new todo"))
       .andExpect(jsonPath("$.completed").value(false));

    assertThat(subject.getTodos()).hasSize(4);
  }
  ...

Problem:

When executing the Junit I end up with this exception:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'todoController': Unsatisfied dependency expressed through field 'todoRepository': No qualifying bean of type [au.com.bla.bla.bla.repository.TodoRepository] found for dependency [au.com.bla.bla.bla.repository.TodoRepository]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [au.com.bla.bla.bla.repository.TodoRepository] found for dependency [au.com.bla.bla.bla.repository.TodoRepository]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:569) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:349) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
…

Can anyone help with this ? Thanks in advance

Greg
  • 411
  • 2
  • 6
  • 15
  • Where did you store your DB config? And why you autowiring repository to controller test? If you want just mock some repository calls you can define it by this way: @MockBean private TodoRepository todoRepository ; – eg04lt3r Oct 15 '16 at 23:29
  • Thanks Alex, I did a mistake describing the issue, If you can have another look that would be great ! I'm using H2 DB (In-memory DB), my db config is just a definition to a datasource in a file called application.yml – Greg Oct 16 '16 at 01:03

1 Answers1

21

Your error is actually the expected behavior of @WebMvcTest. You basically have 2 options to perform tests on your controller.

1. @WebMvcTest - need to use @MockBean

With @WebMvcTest, a minimal spring context is loaded, just enough to test the web controllers. This means that your Repository isn't available for injection:

Spring documentation:

@WebMvcTest will auto-configure the Spring MVC infrastructure and limit scanned beans to @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver.

Assuming the goal is just to test the Controller, you should inject your repository as a mock using @MockBean.

You could have something like:

@RunWith(SpringRunner.class)
@WebMvcTest(TodoController.class)
public class TodoControllerTest {

    @Autowired
    private MockMvc mvc;

    @Autowired
    private TodoController subject;

    @MockBean
    private TodoRepository todoRepository;

    @Test
    public void testCreate() throws Exception {
    String json = "{\"text\":\"a new todo\"}";

    mvc.perform(post("/todo").content(json)
                             .contentType(APPLICATION_JSON)
                             .accept(APPLICATION_JSON))
       .andExpect(status().isOk())
       .andExpect(jsonPath("$.id").value(3))
       .andExpect(jsonPath("$.text").value("a new todo"))
       .andExpect(jsonPath("$.completed").value(false));

        Mockito.verify(todoRepository, Mockito.times(1)).save(any(Todo.class));
    }
}

2. @SpringBootTest - you can @Autowire beans

If you want to load the whole application context, then use @SpringBootTest: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html

You'd have something like this:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class TodoControllerTest {

    private MockMvc mvc;

    @Autowired
    TodoController subject;

    @Autowired
    private WebApplicationContext context;

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

    @Test
    public void testNoErrorSanityCheck() throws Exception {
        String json = "{\"text\":\"a new todo\"}";

        mvc.perform(post("/todo").content(json)
                .contentType(APPLICATION_JSON)
                .accept(APPLICATION_JSON))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.id").value(3))
        .andExpect(jsonPath("$.text").value("a new todo"))
        .andExpect(jsonPath("$.completed").value(false));

        assertThat(subject.getTodos()).hasSize(4);
    }
}
alexbt
  • 16,415
  • 6
  • 78
  • 87
  • Thanks Alex, I did a mistake describing the issue, If you cab have another look that would be great ! – Greg Oct 16 '16 at 00:58
  • Isn't the issue still is the same ? You autowire TodoController in a test annotated with WebMvcTest, so the whole spring context ISN'T loaded. So you need to declare "@MockBean TodoRepository", otherwise TodoController fails to get autowired with the error "cannot find bean TodoRepository". – alexbt Oct 16 '16 at 01:00
  • 1
    If you want your repository + h2-memory database loaded, you should use @SpringBootTests instead of WebMvcTest, see the link in my answer (and example on the web) – alexbt Oct 16 '16 at 01:07
  • Thanks Alex, but I still need WebMvcTest and if I remove it i get this : – Greg Oct 16 '16 at 03:48
  • Unsatisfied dependency expressed through field 'mvc': No qualifying bean of type [org.springframework.test.web.servlet.MockMvc] found for dependency – Greg Oct 16 '16 at 03:48
  • You can't "just remove WebMvcTest", in my answer I suggested to use @SpringBootTest as an alternative. See my answer, I've updated with 2 code sample, 1 with WebMvcTest, and the other with SpringBootTest. – alexbt Oct 16 '16 at 16:13
  • Hi, Alex, do you know how I can mock the Spring Security with Spring Social using the second variant? I have posted my question [here](http://stackoverflow.com/questions/41121184/withuserdetails-does-not-seem-to-work). – Jagger Dec 13 '16 at 13:52
  • I added a potential solution (to your question) – alexbt Dec 13 '16 at 16:54
  • 1
    I wish I could upvote this answer 50 times! Thank you, I've been stuck on this for a couple of days now. – prola Jan 30 '18 at 04:59
  • 1
    @prola 50 upvotes x 10 points/upvote = 500 points. You could always offer a bounty of 500 points for this answer. – pacoverflow Apr 06 '18 at 23:18