3

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.

  • can you remove the `json` from `post("/api/enforcementactions/action", json)`. This overloaded `.post()` takes uri variables. Also do you have any Bean Validation in place? And if this does not work, do you have a valid JSON from e.g. testing with Postman as a fallback? – rieckpil Jul 22 '20 at 17:00
  • I get the same results if I remove json from the uri, No Bean validation in place. Yes, it works in postman, I will update OP above to show all this. – reactFullStackDeveloper Jul 22 '20 at 17:04

2 Answers2

2

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());
rieckpil
  • 10,470
  • 3
  • 32
  • 56
1

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.

  • 1
    good that you found the issue. You can configure the `ObjectMapper` that you instantiated by registering the Java Time module and setting a serialization feature to make it work: https://codeboje.de/jackson-java-8-datetime-handling/ – rieckpil Jul 22 '20 at 19:05
  • I just had the same issue. Browsed for a solution frantically, until I came upon this article: https://codeboje.de/jackson-java-8-datetime-handling/ . For the next lost soul, I hope this helps you. :) – gourabix Aug 02 '20 at 08:25