martine
martine

Reputation: 79

Testing method Controller in Spring with mockito

@PostMapping(path = "/add")
public @ResponseBody ResponseEntity<String> addAccount(@RequestBody Account account) {
    if (account != null && account.getUserName() != null && account.getUserPass() != null) ) {
        if (accountRepository.countUserName(account.getUserName()) > 0) {
            return new ResponseEntity<String>("Account",HttpStatus.BAD_REQUEST);
        }
        account.setUserPass(bCryptPasswordEncoder.encod(account.getUserPass()));
        accountRepository.AccountSaveTo(account);
        return new ResponseEntity<String>("Saved",HttpStatus.OK);
    }
    return new ResponseEntity<String>("Invalid",HttpStatus.BAD_REQUEST);
}

I must test this method of AccountController. I am testing the method in Spring with the Mockito library. How I can test this method? I previously wrote the following unit test:

@Test
public void shouldSaveAccountToDb() {
    Account acc = new Account();
    acc.setUser(this.user);
    acc.setEmail(this.mail);
    String account = this.accountController.addAccount(this.user, this.mail);
    verify(this.accountRepepetytory)).save(Mockito.refEq(acc, new String[0]));
    Assert.assertThat(account, CoreMatchers.is("Account"));
}

but this method was improved and changed and I must write a new unit test, but I do not know how and where to start. Could you help, even with pseudocode?

Upvotes: 1

Views: 10206

Answers (3)

Eduardo Meneses
Eduardo Meneses

Reputation: 524

Once we are talking about Mockito we must have in mind we are trying unit tests only, right? I mean, no integrations related and so on.

I felt a little bit confuse with your test because you posted some class (I suppose it is your controller) but your are expecting to call some method from your AccountRepository, but even if you call that you are observing the reference of parameter your are sending, not the values themselves.

Personally, exploring what I understood from your tests, I would do this:

@RunWith(MockitoJUnitRunner.class)
public class AccountControllerTest {
   @Spy
   @InjectMocks
   private AccountController controller;

   @Mock
   private AccountRepository repository;

   @Test
   public void shouldSaveAccountToDb() {
      Account acc = new Account();
      acc.setUser(this.user);
      acc.setEmail(this.mail);

      doReturn(0).when(repository).countUserName(this.user);
      doReturn("Saved").when(repository).accountSaveTo(acc);

      ResponseEntity<String> result = controller.addAccount(acc);

      verify(repository, times(1)).accountSaveTo(acc);
      Assert.assertEquals("Saved", result.getEntity());

}

Explaining what I did:

Based on you test name, I suppose you want to have the proper result only if it saves in the DB. The "Account" is returned not when it is saving on DB, but when that account already exists there, so your test naming is not reflecting what it does in fact.

Considering it, I fixed the expectations for this test to reflect correctly what it is doing.

After that I used @Spy annotation (which enters in the real methods for the annotated class) for your controller and @Mock (which doesn't enter the real methods for the annotated class) for the external components. @InjectMocks add all mocks to the annotated instance.

Once we already have the component (unit) we want to test isolated, I will MOCK the results from external components like doing this:

doReturn({expected_return}).when({mock}).{method_name}({parameters});

Now, when your test run, the code will answer what you configured for that component inside that @Spy component, and NOW you are just testing the unit and accessing the real method to have its coverage in the future.

I hope I helped you, sir :)

Upvotes: 2

Sean Patrick Floyd
Sean Patrick Floyd

Reputation: 298818

Testing Spring Controllers with classic Unit tests is pretty worthless, as Spring relies on lots of magic around the controllers. You can get much more expressive and reliable tests by using the Spring MVC Test framework (formerly known as MockMVC). It allows you to fire virtual requests at the controller and make assertions based on the virtual response (virtual means no network traffic is involved, all requests, sessions and responses are very smart mocks).

An example test can look like this:

@Test
void getAccount() throws Exception {
    this.mockMvc.perform(get("/accounts/1").accept(
        MediaType.parseMediaType("application/json;charset=UTF-8"))
    )
    .andExpect(status().isOk())
    .andExpect(content().contentType("application/json"))
    .andExpect(jsonPath("$.name").value("Lee"));
}

Upvotes: 2

Glim
Glim

Reputation: 361

To do that you can use MockMvc lib.

Also, try this tutorial: https://memorynotfound.com/unit-test-spring-mvc-rest-service-junit-mockito/

A sample of code using this technologies:

@SpringBootTest
@AutoConfigureMockMvc
@DirtiesContext(classMode=ClassMode.AFTER_CLASS, methodMode=MethodMode.BEFORE_METHOD)
@AutoConfigureTestDatabase
public class AccountRestTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void testPostNewEntity() throws Exception {
        Account account = new Account("dummy");
        MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/add")
                .content(ClassToStringUtils.parse(account)).contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().is(HttpStatus.CREATED.value()))
                .andReturn();
    }
}

// EDIT

ClassToStringUtils.parse(Object obj) just parses an object to a json format String.

Upvotes: 0

Related Questions