Java  Backend Developer
Java Backend Developer

Reputation: 129

Mock Repository From Controller class using Spring Boot and JUnit

I am writing the test cases for my controller class which is a Spring Boot Application and I want to write the test cases only for controller class which invokes Service and Service to Repository. I am using SpringBootTest which is used for to create the instances for all of my beans. I want to mock only Database calls and external api calls.

MyController {

    @Autowired
    MyService service;

    //Api call for getDetails
    service.getDetails();
}


MyService {

   @Autowired
   MyRepository repo;

}

MyControllertest {
    @Autowired
    MyController controller;

    @Mock
    MyRepository repoMock;

    @Before
    public void setup(){
        // intit mocks
    }

    @Test
    public void myTest(){
        when(repoMock.getDetails()).thenReturn(null);
        controller.getdetails();
    }
}

When I run the test case, it is not using the mock Repository, instead of that using the @Autowired Repository which is mentioned in the Service class.

Can anyone please explain me how to mock the repository from controller class.

Posting so many questions in this blog, but am not getting any responses.

Upvotes: 6

Views: 18545

Answers (5)

Hristo Grozdanov
Hristo Grozdanov

Reputation: 11

This works fine:

@WebMvcTest(MyController.class)
public class WebMockTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private MyRepository repository;

@SpyBean
private MyService service;

@InjectMocks
private MyController controller;

@Test
public void saveTest() throws Exception {
    String content = "";
    long id=Long.MAX_VALUE;

    Entity entityToSend = new Entity(id, content);
    Gson gson = new Gson();
    when(repository.save(any(Entity.class)).thenReturn(Optional.of(entityToSend));

    mockMvc.perform(MockMvcRequestBuilders
            .post("/api/save")
            .content(gson.toJson(entityToSend))
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(Long.MAX_VALUE))
            .andExpect(MockMvcResultMatchers.jsonPath("$.content").value(content));
}

Upvotes: 0

Kamil W
Kamil W

Reputation: 2376

This worked for me:

  @MockBean
  private OrderRepository orderRepository;

  @SpyBean
  private OrderService orderService;

  @InjectMocks
  private OrderController orderController;

Upvotes: 2

pvpkiran
pvpkiran

Reputation: 27068

It is not using your Mocks because you are not injecting those mocks into your controller / service classes. Instead you are Autowiring it.
Correct way to do it is

@RunWith(MockitoJUnitRunner.class)
public class MyControllerTest {

    @InjectMocks
    MyController controller;
    .....

}

Even better solution would be to get rid of Field Injection and use Constructor Injection

For example in your Controller and Service class. Instead of using @Autowired on Fields you can do it on Contructor. For example

class MyService {

   private final MyRepository repo;

   @Autowired
   public MyService(final MyRepository repo) {
      this.repo = repo;
   }
}

Similarly in Controller class

class MyController {

    @Autowired
    private final MyService service;

    public MyController(final MyService service) {
         this.service = service
    }
}

Doing this way will help you in setting mocks easily during runtime. For example in your test class you could do

@RunWith(MockitoJUnitRunner.class)
public class MyControllertest {

    MyController controller;
    MyService service;

    @Mock
    MyRepository repoMock;


    @Before
    public void setup(){
        // init mocks
      service = new MyService(repoMock);
      controller = new MyController(service);
    }
  ..............
}

Here is a nice article about Field injection

Upvotes: 6

JSONStatham
JSONStatham

Reputation: 373

3 ways to solve this:

1.One is with with Mockito.

 @Mock
 MyRepository repoMock;
 @InjectMocks
 MyService service;

What this does is it matches the MyService class fields by Class type, and assigns the Mocked variables to those fields. So it injects the mocks to your service. This has nothing to do with Spring.

2.Refactor the MyService constructor, so dependencies can be Injected via Constructor. @Autowired works with constructors also, and because of this it's the preferred way.

@Autowired
public MyService(MyRespoitory repository){
  this.repository = repository;
}

3.Use Springs test environment:

https://docs.spring.io/spring-security/site/docs/current/reference/html/test-mockmvc.html

And define a bean that is a mocked version of your repository

@Bean
public MyRepository mockMyRepository(){
  return mock(MyRespository.class);
}

This can be slow and tiresome for Service testing.

Second one is the most preferred and easiest way.

Upvotes: 0

Stefan Birkner
Stefan Birkner

Reputation: 24540

You must not call

MockitoAnnotations.initMocks(this);

in the setUp method because the SpringRunner already initializes the mocks. By calling initMocks you assign a new object to repoMock. All Mockito actions like when are performed on the new object, but Spring still uses the "old" MyRepository that was created by the SpringRunner.

Upvotes: 0

Related Questions