1

I want to test the post method that should send a post request to "server" (So I want to mock the response from the server and check the response). Also, I want to test that the response contains http status OK in the body. Question: How should I do that with mockito?

My Post Method in the client (Client-side):

public class Client{
        public static void sendUser(){

        String url = "http://localhost:8080/user/add";

        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.setContentType(MediaType.APPLICATION_JSON);
        requestHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));

        User test = new User();
        test.setName("test");
        test.setEmail("a@hotmail.com");
        test.setScore(205);

        RestTemplate restTemplate = new RestTemplate();
        HttpEntity<User> request = new HttpEntity<>(test);

        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, request, String.class);

        if(response.getStatusCode() == HttpStatus.OK){
            System.out.println("user response: OK");
        }

      }
  }

My Controller in another module (server-side):

@RestController
@RequestMapping("/user")
public class UserController
{
    @Autowired
    private UserRepository userRepository;

    @PostMapping("/add")
    public ResponseEntity addUserToDb(@RequestBody User user) throws Exception
    {
        userRepository.save(user);
        return ResponseEntity.ok(HttpStatus.OK);
    }

Test:

    @RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest(classes = Client.class)
@AutoConfigureMockMvc
public class ClientTest
{

    private MockRestServiceServer mockServer;

    @Autowired
    private RestTemplate restTemplate; 

    @Autowired
    private MockMvc mockMvc;

    @Before
    public void configureRestMVC()
    {
        mockServer =
                MockRestServiceServer.createServer(restTemplate);
    }

    @Test
    public void testRquestUserAddObject() throws Exception
    {

        User user = new User("test", "mail", 2255);

        Gson gson = new Gson();

        String json = gson.toJson(user );

        mockServer.expect(once(), requestTo("http://localhost:8080/user/add")).andRespond(withSuccess());


        this.mockMvc.perform(post("http://localhost:8080/user/add")
                .content(json)
                .contentType(MediaType.APPLICATION_JSON))
                .andDo(print()).andExpect(status().isOk())
                .andExpect(content().json(json));
    }

}

And now i am getting this error:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'ClientTest': Unsatisfied dependency expressed through field 'restTemplate'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.web.client.RestTemplate' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
NaN
  • 153
  • 1
  • 5
  • 14
  • _test the post method that should send a post request to server_ , So you want a real server call not **mock** ? – Vivek Mar 16 '19 at 20:40
  • No I want to mock the response – NaN Mar 16 '19 at 20:41
  • Then looks like not _send a post request to server_ , rather you want to mock the response for some POST request ? Is that right? – Vivek Mar 16 '19 at 20:43
  • Yeah I want to mock the POST response for this specifiec POST request – NaN Mar 16 '19 at 20:46
  • https://stackoverflow.com/questions/39486521/how-do-i-mock-a-rest-template-exchange, https://stackoverflow.com/questions/50271164/how-do-i-mock-resttemplate-exchange and http://www.javased.com/?api=org.springframework.web.client.RestTemplate these are some helpful links ..would suggest give it a try , if stuck update your question. – Vivek Mar 16 '19 at 20:52
  • Alright will do. Thanks for the suggestions – NaN Mar 17 '19 at 12:49
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/190196/discussion-between-vibrantvivek-and-monigma). – Vivek Mar 17 '19 at 13:14

2 Answers2

2

Based on your Client class , would like to suggest below changes so as to make it better testable :

    //  class
    public class Client {

        /*** restTemplate unique instance for every unique HTTP server. ***/
        @Autowired
        RestTemplate restTemplate;

        public ResponseEntity<String> sendUser() {

        String url = "http://localhost:8080/user/add";

        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.setContentType(MediaType.APPLICATION_JSON);
        requestHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));

        User test = new User();
        test.setName("test");
        test.setEmail("a@hotmail.com");
        test.setScore(205);

        HttpEntity<User> request = new HttpEntity<>(test);

        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, request, String.class);

        if(response.getStatusCode() == HttpStatus.OK){
            System.out.println("user response: OK");
        }
        return response;
      }
  }

And then for above we Junit as :

@RunWith(MockitoJUnitRunner.class)
public class ClientTest {

  private String RESULT = "Assert result";

  @Mock
  private RestTemplate restTemplate;

  @InjectMocks
  private Client client;

  /**
   * any setting needed before load of test class
   */
  @Before
  public void setUp() {
    // not needed as of now
  }

  // testing an exception scenario
  @Test(expected = RestClientException.class)
  public void testSendUserForExceptionScenario() throws RestClientException {

    doThrow(RestClientException.class).when(restTemplate)
        .exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), any(Class.class));
    // expect RestClientException
    client.sendUser();
  }

  @Test
  public void testSendUserForValidScenario() throws RestClientException {

    // creating expected response
    User user= new User("name", "mail", 6609); 
    Gson gson = new Gson(); 
    String json = gson.toJson(user); 
    doReturn(new ResponseEntity<String>(json, HttpStatus.OK)).when(restTemplate)
        .exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), any(Class.class));
    // expect proper response
    ResponseEntity<String> response =
        (ResponseEntity<String>) client.sendUser();
    assertEquals(this.RESULT, HttpStatus.OK, response.getStatusCode());
  }
}

Basically in your sendResponse() function, we are doing as:

// we are getting URL , creating requestHeader
// finally creating HttpEntity<User> request 
// and then passing them restTemplate.exchange 
// and then restTemplate is doing its job to make a HTPP connection and getresponse...
// and then we are prinnting the response... somestuff 

Thus in its corresponding test we should also test only what the function is doing since the connection is being taken care by restTemplate and you're not overriding any working of restTemplate so we should not do anything for the same... rather just test our code/logic.

Lastly, to be sure imports looks like :

to be sure , imports will be like :

import org.springframework.http.HttpEntity; 
import org.springframework.http.HttpHeaders; 
import org.springframework.http.HttpMethod; 
import org.springframework.http.HttpStatus; 
import org.springframework.http.MediaType; 
import org.springframework.http.ResponseEntity; 
import org.springframework.web.client.RestTemplate;

Hope this helps.

Vivek
  • 895
  • 11
  • 23
  • Hey VibrantVivek, I am getting errors now when I execute the addUser() method because the restTemplate is autowired and I don't really want to run the Client class with SpringBoot. Therefore, it is giving an error that says that the restTemplate is NuLL. So how can I make it run without autowiring it and still make the tests work? – NaN Mar 17 '19 at 18:56
  • It doesn't need to be a Springboot application to use Autowired..rather only Springwebmvc should do. Lets Continue: [here](https://chat.stackoverflow.com/rooms/190196/discussion-between-vibrantvivek-and-monigma) – Vivek Mar 17 '19 at 19:02
1

Full code first (explanation is below):

import static org.springframework.test.web.client.ExpectedCount.manyTimes;
import static org.springframework.test.web.client.ExpectedCount.once;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest
@AutoConfigureMockMvc
public class MyTestClass {

MockRestServiceServer mockServer;

    @Autowired
    private RestTemplate restTemplate;  //create a bean somewhere. It will be injected here. 

    @Autowired
    private MockMvc mockMvc;

    @Before
    public void configureRestMVC(){
        mockServer =
                MockRestServiceServer.createServer(restTemplate);
    }

    @Test
    public void test0() throws Exception {
        //this is where you would mock the call to endpoint and and response
        mockServer.expect(once(), requestTo("www.example.com/endpoint1"))
        .andRespond(withSuccess());
    ... 
    //here you will actually make a call to your controller. If the service class is making a post call to another endpoint outside, that you just mocked in above statement.
    this.mockMvc.perform(post("www.example2.com/example2endpoint")
                .content(asJsonString(new YouCustomObjectThatYouWantToPost))
                .contentType(MediaType.APPLICATION_JSON))
        .andDo(print()).andExpect(status().isOk())
        .andExpect(content().json(matchResponseAgainstThisObject()));
   }

You would need to use @AutoConfigureMockMvc annotation. The purpose behind is is to not start the server at all, but test only the layer below that, where Spring handles the incoming HTTP request and hands it off to your controller. That way, almost the full stack is used, and your code will be called exactly the same way as if it was processing a real HTTP request, but without the cost of starting the server. To do that we will use Spring’s MockMvc, and we can ask for that to be injected for us by using the @AutoConfigureMockMvc annotation on the test class.

private MockRestServiceServer mockServer;

MockRestServiceServer is a main entry point for client-side REST testing. Used for tests that involve direct or indirect use of the RestTemplate. Provides a way to set up expected requests that will be performed through the RestTemplate as well as mock responses to send back thus removing the need for an actual server.

mockServer.expect(once(), requestTo("www.example.com/endpoint1"))
    .andRespond(withSuccess());

This is where you would setup mocking to outside calls. And setup expectations as well.

this.mockMvc.perform(post("www.example2.com/example2endpoint")..

This is where you would actually make a rest/api call to your own endpoint, the one that you defined in your controller. Spring will hit your endpoint, perform all the logic that you have in your controller/service layer, and when it comes to the part of actually making a call outside, will use mockServer that you just defined above. That way, it is totally offline. You never hit the actual outside service. Also, you will append your assertions on the same mockMvc.perform method.

Faraz
  • 6,025
  • 5
  • 31
  • 88
  • `@ActiveProfiles("test")`: what is the point of having this and what does it do? `@Autowired private RestTemplate restTemplate;` : the resttemplate is in my client code (so in different module) so how is it possible to autowire this? – NaN Mar 16 '19 at 21:16
  • Usually, you would have some beans only for testing. Or you may have some beans that may will have different behavior for different profiles. So when you put `@ActiveProfiles("test")`, only those beans are instantiated that are marked with `@Profile("test")`. Also, this `@ActiviveProfiles("test")` will activate the test profile. Secondly, if you already have restTemplate bean defined else where, then no need to worry. Spring will automatically autowire it here in this class for you. And if it still doesn't work, then you can provide a class name that has a resttemplate bean like this: – Faraz Mar 16 '19 at 21:26
  • `@SpringBootTest(classes = ClassNameWhichContainsBeanDefinitionOfRestTemplate.class))` – Faraz Mar 16 '19 at 21:28
  • @MoNigma try this, instead of autowiring it, just create a new instance of RestTemplate. Try that. – Faraz Mar 16 '19 at 21:31
  • I have updated the question. Please take a look at what I have now, because I am a bit confused right now (I am new to this advanced testing with mockito). – NaN Mar 16 '19 at 21:57
  • Oh. I understand your question. You want to test your controller itself. For that, you would not need Mockito. This is a perfect example for you: https://spring.io/guides/gs/testing-web/ It has precisely what you are looking for. – Faraz Mar 16 '19 at 22:32
  • You can ignore my answer for now. Just follow this https://spring.io/guides/gs/testing-web/ It has what you are looking for. – Faraz Mar 16 '19 at 22:39
  • Well I want to test controller indirectly (so via requesting an object to the controller to add it to my db and then send me an OK response). But what I want to do is mock this behaviour of the controller and test only the method that I have in the client. Ultimately, what I want to test to get my branch coverage up is the if statement that checks whether the OK response has been sent (and I want to test everything before that aswell but with mockito). I hope this clarifies what I want to achieve. – NaN Mar 16 '19 at 23:10
  • are you trying to mock a call to database? – Faraz Mar 16 '19 at 23:51
  • No only the response and the request to controller – NaN Mar 17 '19 at 10:00
  • You have no clear picture what you want to test and why. – Faraz Mar 17 '19 at 10:15
  • 1) I want to check that the right request has been sent (using mockito) 2) I want to check the right response has been sent (using mockito) 3) Lastly, I want to test whether getstatuscode was OK, so this part: `if(response.getStatusCode() == HttpStatus.OK){ System.out.println("user response: OK"); }` THat is it nothing else. – NaN Mar 17 '19 at 10:30
  • You cannot mock the very thing you are testing. How can you mock your endpoint when that is the endpoint you are testing? You cannot do that. Let me be explicit... you cannot mock `http://localhost:8080/user/add` because this is the endpoint that you are testing. What you can do is, mock the userRepository call. – Faraz Mar 17 '19 at 10:38
  • Yeah but here is the thing: I don't want to test the controller or the repository whether they work or not. I just want to mock the behaviour of the request and response. So I know beforehand what the response should be and what the request should be. So I want to use mockito to test whether the response and the request really do what I expect them to do without interfering with server at all. – NaN Mar 17 '19 at 11:36
  • Okay you want to mock the behavior of the request to `http://localhost:8080/user/add`. I got it. **How** do you intend to do that? Where will you trigger it from? How will you trigger it? What URL will you hit that will automatically or internally hit `http://localhost:8080/user/add`? – Faraz Mar 17 '19 at 16:58
  • I have solved it with the help of @VibrantVivek , so you can check his answer. – NaN Mar 17 '19 at 17:51
  • Thanks for your help aswell though :) – NaN Mar 17 '19 at 20:40