1

So I am trying to make a test for the post method of my controller, but I keep finding tests from other people that do not work on my or their post methods are way more advanced.

My post method

@Autowired
PartyLeaderService partyleaderService;

@PostMapping("/")
public void add(@RequestBody PartyLeaderDto partyLeaderDto){
    partyLeaderService.savePartyLeader(partyLeaderDto);
}

As you can see it is fairly simple, but I still can not seem to get it to work properly. I tried this method:

@Test
public void testPostExample() {
    PartyLeaderDto partyLeaderDto = new PartyLeaderDto(1, "arun", "link");

    partyLeaderController.add(partyLeaderDto);
    Mockito.verify(partyLeaderController).add(partyLeaderDto);
}

But SonarQube is saying that the method is still not covered by tests, so my test must be wrong.

My DTO

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Builder
public class PartyLeaderDto {
    private Integer id;
    private String name;
    private String apperance;
}

Can anyone help me, because I think the answer is fairly simple, but I can't seem to find it.

LardinoisJosse
  • 363
  • 2
  • 5
  • 22

3 Answers3

0

The issue with your unit test is that you're verifying a call over something that is not a mock

Mockito.verify(partyLeaderController).add(partyLeaderDto);

partyLeaderController is not a mock as this is what you're looking to test at the moment. What you should be mocking and verifying is PartyService.

  @Test
  public void testPostExample() {
    PartyService partyService = mock(PartyService.class);
    PartyLeaderController controller = new PartyLeaderController(partyService);
    PartyLeaderDto partyLeaderDto = new PartyLeaderDto(1, "arun", "link");

    controller.add(partyLeaderDto);
    
    Mockito.verify(partyService).saveParty(partyLeaderDto);
  }

However this is not a very good approach to test the web layer, as what you should be looking to cover with your test is not only whether PartyService is called but also that you're exposing an endpoint in a given path, this endpoint is expecting the POST method, it's expecting an object (as json perhaps?) and it's returning a status code (and maybe a response object?)

An easy, unit test-like approach to cover this is to use MockMvc.

  @Test
  public void testPostExample() {
    PartyService partyService = mock(PartyService.class);
    MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new PartyLeaderController(partyService)).build();
    PartyLeaderDto partyLeaderDto = new PartyLeaderDto(1, "arun", "link");

    mockMvc.perform(
        post("/party")
            .contentType(MediaType.APPLICATION_JSON)
            .content(new ObjectMapper().writeValueAsString(partyLeaderDto))
    ).andExpect(status().isOk());
    verify(partyService).saveParty(partyLeaderDto);
  }
Yayotrón
  • 1,759
  • 16
  • 27
  • I am getting the following error on this line: MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new PartyLeaderController(partyService)) Expected 0 arguments but found 1 My controller does not have a constructor. Should I make one with the service in it? – LardinoisJosse May 17 '21 at 11:44
  • Also when I edit that line to this: MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new PartyLeaderController()); I get the following error: Required type: MockMvc Provided: StandaloneMockMvcBuilder – LardinoisJosse May 17 '21 at 11:46
  • Yes, it's recommended to use constructor injection vs field-level injection, one of the key benefits of this is that it's easier to test :) here's some referrence about it https://stackoverflow.com/questions/40737720/constructor-injection-vs-field-injection – Yayotrón May 17 '21 at 11:46
  • Oops, I forgot .build() at the end, will edit my answer. – Yayotrón May 17 '21 at 11:48
  • Now this is the next error I get: Wanted but not invoked: partyLeaderService.savePartyLeader( com.example.demo.DTO.PartyLeaderDto@3136e15a ); -> at com.example.demo.Controller.PartyLeaderEndpointTests.testPostExample(PartyLeaderEndpointTests.java:97) Actually, there were zero interactions with this mock. – LardinoisJosse May 17 '21 at 12:01
  • That sounds like the test doing its work, means your method is not being called. If you blindly copied the code I gave, then I think you need to replace post("/party") with the path where your endpoint is exposed, perhaps it's just post("/") ? – Yayotrón May 17 '21 at 12:05
  • Nope I checked that and that is not the problem – LardinoisJosse May 17 '21 at 12:32
  • Debug the unit test and place a breakpoint in your controller's method, should be quite easy to identify whether your method is being called and this method is calling partyService. – Yayotrón May 17 '21 at 12:39
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/232517/discussion-between-lardinoisjosse-and-yayotron). – LardinoisJosse May 17 '21 at 13:01
0

What you are doing here is actually a unit test by verifying a stub call.

Either you can unit test by mocking the behaviour using Mockito's when(....).then(...) and then using assertion to assert the results.

or You can follow this article to use a MockMvc to do an integration test step by step- https://www.baeldung.com/integration-testing-in-spring

Caffeine Coder
  • 948
  • 14
  • 17
0

With the help of Yayotron I got to the answer. This is the code:

@Test
@WithMockUser("Test User")
public void testPostParty() throws Exception {
    PartyLeaderService partyLeaderService = mock(PartyLeaderService.class);
    MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new PartyLeaderController(partyLeaderService)).build();
    PartyLeaderDto partyLeaderDto = new PartyLeaderDto(1, "arun", "link");

    mockMvc.perform(
            MockMvcRequestBuilders.post("http://localhost:8080/partyleaders/")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(new ObjectMapper().writeValueAsString(partyLeaderDto))
    ).andExpect(status().isOk());
    Mockito.verify(partyLeaderService).savePartyLeader(ArgumentMatchers.refEq(partyLeaderDto));
}

Thanks a lot for helping!

LardinoisJosse
  • 363
  • 2
  • 5
  • 22