MockMvc POST returning 400 - expecting 201

Viewed 592

I'm trying to write a controller unit test for a @PostMapping but am getting a failed test Status expected:<201> but was:<400>

The controller works as expected in Postman so I know it actually works, but it would be nice to have a working unit test as well. What am I doing wrong?

TEST

   @Test
    @DisplayName("CREATE NEW ENFORCEMENT ACTION")
    void testCreateNewEnforcementAction() throws Exception {
    EnforcementAction mockAction = new EnforcementAction();

    mockAction.setSystemId(1289);
    mockAction.setCurrentStaff("ralbritton");
    mockAction.setCurrentStatus("NEEDED");
    mockAction.setCreatedOn(LocalDateTime.now());
    mockAction.setCreatedBy("ralbritton");
    mockAction.setEaType("IF");
    mockAction.setEaCode("CAP");
    mockAction.setDeleted(false);

    ObjectMapper objectMapper = new ObjectMapper();
    String json = objectMapper.writeValueAsString(mockAction);

    mockMvc.perform(MockMvcRequestBuilders.post("/api/enforcementactions/action")
            .contentType(MediaType.APPLICATION_JSON)
            .content(json)
            .characterEncoding("utf-8"))
     .andExpect(status().isCreated()); //Have also tried this as .isOK() (didn't make a diff)
     //.andReturn(); ///Added and removed this line to see if it made a differnce (it did not)
    }

CONTROLLER BEING TESTED

 @PostMapping("/api/enforcementactions/action")
    public ResponseEntity<?> createNewEnforcementAction(@RequestBody EnforcementAction newAction) {
        service.createEnforcementAction(newAction);
        return new ResponseEntity<>(newAction, HttpStatus.CREATED);
    }

MODEL UPDATE: I'm adding in the model to show that there is not Bean Validation on fields

public class EnforcementAction {

    private Integer eaId;
    private Integer systemId;
    private String alternateNumber;
    private String systemName;
    private Integer tenschdId;
    private String currentStaff;
    private String currentStatus;
    private LocalDate dateActionIssued;
    private LocalDate dateActionClosed;
    private boolean deleted;
    private LocalDateTime createdOn;
    private String createdBy;
    private LocalDateTime modifiedOn;
    private String lastModifiedBy;
    private String eaType;
    private String eaCode;
    private String comment;
    private Long daysSinceCreate;

    private List<EaStaffHistory> staffAssigned = new ArrayList<>();
    private List<EaDocStatusHistory> documentStatus = new ArrayList<>();
    private List<EaComments> eaComments = new ArrayList<>();

    /** Constructors */
    public EnforcementAction() {
    }

    public EnforcementAction(Integer eaId, Integer systemId, String systemName, Integer tenschdId,
                             String currentStaff, String currentStatus, Long daysSinceCreate,
                             String createdBy, String lastModifiedBy, LocalDate dateActionIssued, LocalDate dateActionClosed,
                             String eaType, String eaCode, LocalDateTime createdOn) {
        this.eaId = eaId;
        this.systemId = systemId;
        this.tenschdId = tenschdId;
        this.systemName = systemName;
        this.currentStaff = currentStaff;
        this.currentStatus = currentStatus;
        this.createdBy = createdBy;
        this.lastModifiedBy = lastModifiedBy;
        this.dateActionClosed = dateActionClosed;
        this.dateActionIssued = dateActionIssued;
        this.eaType = eaType;
        this.eaCode = eaCode;
        this.daysSinceCreate = daysSinceCreate;
        this.createdOn = createdOn;
    }

    ...getters and setters....

POSTMAN showing successful post: enter image description here

EDIT: I've updated the OP code to reflect current state. Still having the same issue though.

2 Answers

The reason for the 400 is how you send your payload to your controller. You are not serializing the Java object to JSON, but use the .toString() representation of it:

.content(String.valueOf(mockAction)))

Either make use of the ObjectMapper or prepare a custom JSON string:

ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(mockAction);

mockMvc.perform(MockMvcRequestBuilders
        .post("/api/enforcementactions/action")
        .contentType(MediaType.APPLICATION_JSON)
        .content(json))
        .andExpect(status().isCreated());

OK so I finally figured out my problem and I'm posting it here in case someone else has the same issue. While @Rieckpil was correct in all his suggestions (and I will mark his answer as correct) the other problem I was having was in my mockAction object. I had:
mockAction.setCreatedOn(LocalDateTime.now())
Even though createdOn is of type LocalDateTime it was getting deconstructed in the body to look like this:

"createdOn": {
       "dayOfWeek": "WEDNESDAY",
       "dayOfYear": 204,
       "month": "JULY",
       "year": 2020,
       "dayOfMonth": 22,
       "hour": 12,
       "minute": 43,
       "monthValue": 7,
       "nano": 839000000,
       "second": 10,
       "chronology": {
              "id": "ISO",
              "calendarType": "iso8601"
       }
}

When I passed this as the createdOn variable into Postman I was able to get a meaningful error .HttpMessageNotReadableException: JSON parse error: Expected array or string.; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Expected array or string. at [Source: (PushbackInputStream); line: 12, column: 21] (through reference chain: gov.deq.utah.enforcementactions.models.enforcementActions.EnforcementAction["createdOn"])

The test passed when I removed this. I kept all other suggestions provided.

Related