iuhettiarachchi
iuhettiarachchi

Reputation: 451

How to write unit test for controller in spring boot

I am new to Unit testing and TDD. I want to apply unit testing for my controller and service class which i have written in spring boot.

I have implemented test class using a tutorial. However, I could not implemented it successfully. I have included my current code.

Controller

@RestController
@RequestMapping("/api")
public class MyController {
    private static final Logger LOGGER = LoggerFactory.getLogger(AdminController.class);
    @Autowired
    MyService myService;

    @PostMapping("/create")
    public ResponseEntity<?> createUser(@RequestHeader("Authorization") String token, 
        @RequestBody User user){
        ResponseDTO finalResponse = new ResponseDTO();
        try {
            ResponseEntity<?> entity = myService.create(token, user);             
            finalResponse.setMessageCode(entity.getStatusCode());
            finalResponse.setMessage("Success");
            finalResponse.setError(false);
            ResponseEntity<ResponseDTO> finalEntity = ResponseEntity.ok().body(finalResponse);
        return finalEntity;
        } catch (Exception e) {      
            finalResponse.setMessageCode(HttpStatus.EXPECTATION_FAILED);
            finalResponse.setMessage(e.getMessage());
            finalResponse.setError(true);
            ResponseEntity<ResponseDTO> finalEntity = 
            ResponseEntity.ok().body(finalResponse);
            return finalEntity;
    }
}

ResponseDTO

public class ResponseDTO {
    private HttpStatus messageCode;
    private String message;
    private String messageDetail;
    private Object body;
    private boolean error;

    //setters and getters
}

Current Test Class

@RunWith(SpringRunner.class)
public class MyControllerTest {
    private MockMvc mockMvc;

    @InjectMocks
    private MyController myController;

    @Before
    public void setUp() throws Exception {
    mockMvc = MockMvcBuilders.standaloneSetup(myController).build();
    }

    @Test
    public void testCreateUser() throws Exception {
        mockMvc.perform(post("/api/create")
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.*", Matchers.hasSize(1)));
    }

}

When i run test class i am getting WARN Resolved [org.springframework.web.bind.MissingRequestHeaderException: Missing request header 'Authorization' for method parameter of type String]

What am i doing wrong here? Any help would be grateful.

Upvotes: 1

Views: 5702

Answers (2)

YanetP1988
YanetP1988

Reputation: 1356

Your test could be something like this:

 @Test
public void testCreateUser() throws Exception {
    mockMvc.perform(post("/api/create")
        .accept(MediaType.APPLICATION_JSON)
        .header("AUTH_TOKEN", TOKEN)
        .content(ObjectToJsonUtil.convertObjectToJsonBytes(user)))
        .andExpect(status().isCreated())
        .andExpect(jsonPath("$.*", Matchers.hasSize(1)));
}

you'll have to convert object user to json. So you create a util class for this:

public class ObjectToJsonUtil {
    public static byte[] convertObjectToJsonBytes(Object object)
            throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);

        JavaTimeModule module = new JavaTimeModule();
        mapper.registerModule(module);

        return mapper.writeValueAsBytes(object);
    }

}

Hope it helps!

Upvotes: 2

Behrang Saeedzadeh
Behrang Saeedzadeh

Reputation: 47913

There are a few issues with your test:

1. Request mapping

@PostMapping("/create")
public ResponseEntity<?> createUser(
    @RequestHeader("Authorization") String token, 
    @RequestBody User user)

only matches POST requests that have an HTTP header named Authorization and a request body that can be serialized to User. These are not optional. If they are optional, you should explicitly declare that:

@PostMapping("/create")
public ResponseEntity<?> createUser(
   @RequestHeader(name = "Authorization", required = false) String token, 
   @RequestBody(required = false) User user) {

Assuming that they are required, you should setup MockMvc to send both to your controller:

    @Test
    public void testCreateUser() throws Exception {
        mockMvc.perform(
                post("/api/create")
                  .header("Authorization", "XYZ")
                  .content("{\"firstName\": \"James\", \"lastName\": \"Gosling\"}")
                  .accept(MediaType.APPLICATION_JSON)
                )
               .andExpect(status().isCreated())
               .andExpect(jsonPath("$.*", Matchers.hasSize(1)));
    }

here I have assumed your User class is like this:

public class User {

    private String firstName;

    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

2. Content-Type header

Also, you should set the content type header for your MockMvc request, otherwise the test will fail with 415 - Unsupported Media Type. So your test should look like this:

    @Test
    public void testCreateUser() throws Exception {
        mockMvc.perform(
                post("/api/create")
                  .header("Authorization", "XYZ")
                  .header("Content-Type", "application/json")
                  .content("{\"firstName\": \"James\", \"lastName\": \"Gosling\"}")
                  .accept(MediaType.APPLICATION_JSON)
                )
               .andExpect(status().isCreated())
               .andExpect(jsonPath("$.*", Matchers.hasSize(1)));
    }

3. Mocked dependencies

Apart from that, in your test you have annotated MyController with @InjectMocks but you haven't mocked its MyService' dependency. That will set themyServicefield of your controller tonull. To fix that you need to mockMyService' too:

@RunWith(SpringRunner.class)
public class MyControllerTest {

    private MockMvc mockMvc;

    // Mock
    @Mock
    private MyService myService;

    @InjectMocks
    private MyController myController;

    @Before
    public void setUp() throws Exception {
        mockMvc = MockMvcBuilders.standaloneSetup(myController).build();
    }

    @Test
    public void testCreateUser() throws Exception {
        // Configure mock myService
        when(myService.create(anyString(), any(User.class))).thenReturn(new ResponseEntity<>(HttpStatus.CREATED));

        mockMvc.perform(
                post("/api/create")
                  .header("Authorization", "XYZ")
                  .header("Content-Type", "application/json")
                  .content("{\"firstName\": \"James\", \"lastName\": \"Gosling\"}")
                  .accept(MediaType.APPLICATION_JSON)
                )
               .andExpect(status().isCreated())
               .andExpect(jsonPath("$.*", Matchers.hasSize(1)));
    }

}

4. MyService does not satisfy the test conditions

When everything is hunky dory your controller responds with:

ResponseEntity<ResponseDTO> finalEntity = ResponseEntity.ok().body(finalResponse);

which will return a status code of 200. So you either have to modify your test to expect that:

.andExpect(status().isOk())

or you should update your controller to return with the 201 status code:

ResponseEntity<ResponseDTO> finalEntity = ResponseEntity.created(null).body(finalResponse);

Upvotes: 2

Related Questions