I'm implementing a web API as a micronaut service. I have tests against the GET endpoints working, but cannot get the POST endpoints working correctly. I receive an error response 'BAD REQUEST'. The endpoint works correctly from command line curl
or http
(ie) POST requests.
The controller:
@Controller("/people")
@ExecuteOn(TaskExecutors.IO) //Make sure the controller is executed on a non-main thread pool so it does not block
public class PersonController {
private final DataService dataSvc;
public PersonController(final DataService dataSvc) {
this.dataSvc = dataSvc;
}
//Various GET endpoints
@Post //I also tried adding '(consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON)'. This had no effect
@Status(HttpStatus.CREATED)
public Person addPerson(@Valid @NotNull final Person person) {
final Person p = dataSvc.save(person);
//log it!
return p;
}
}
I have an exception handler handling Throwable
and returning 500 (INTERNAL_SERVICE_ERROR).
I also have a handler for ConstraintViolationException
s, which returns a 412 (PRECONDITION_FAILED) response.
Test that's failing is:
@MicronautTest
public class PersonControllerClient {
@Inject @Client("/people")
HttpClient client;
@Inject DataService dataSvc;
final DataService mock = mock(DataService.class);
//Various tests of the GET endpoints
@Test
@DisplayName("Add a person successfully")
public void testAddPersonHappypath() {
final Person.PersonBuilder pb = Person.builder().firstName("John").lastName("Doe");
final Person in = pb.build();
final Person expected = pb.id("1234").build();
try {
final HttpRequest<Person> request = HttpRequest.POST("/", in).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
//Note - I also tried having the client '/', and the request endpoint '/people', which failed in the same manner
//I also tried `HttpRequest.create(HttpMethod.POST, "/people").body(in)` with same result.
when(mock.save(in)).thenReturn(expected);
final HttpResponse<Person> response = client.toBlocking().exchange(request, Person.class);
assertEquals(HttpStatus.CREATED, response.getStatus());
final Person body = response.body();
assertNotNull(body);
assertEquals(expected, body);
} catch (HttpClientResponseException ex) {
fail(String.format("Received exception (%s) - Status %d", ex.getMessage(), ex.getStatus().getCode()));
}
}
}
As far as I can see, this should succeed, but it is going to the fail
(catch block).
I've tried stepping through the test and it doesn't seem to be making it into the PersonController
code at all, so it's something with how the POST query is being constructed.
I'm assuming I'm creating the client incorrectly, but cannot see where. Any help would be appreciated.
Note - I'm moving from SpringBoot to Micronaut. Not sure if I'm trying to apply Spring concepts where they shouldn't be!
More info
I have added tests for the DELETE method, which succeed (both happy and unhappy paths). It's ONLY the POST requests that fail, with BAD REQUEST
Also, probably useful to have the Person
data object
@MappedEntity
@Data
@EqualsAndHashCode
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Person {
@Id
@GeneratedValue
String id;
@NotEmpty(message="First name cannot be empty")
String firstName;
@NotEmpty(message="Last name cannot be empty")
String lastName;
int age;
}
Lombok annotations to make this 'record'ish. Not actually using Record
as the validation annotations can't be applied to fields of a record (target
for NotEmpty
and Id
annotations does not include RECORD_COMPONENT
)
Answer from comment from @szymon-stepniak
Within the post method declaration, make sure there is a
@Body
annotation on the incoming data packetUnsure why the endpoint would work but the test client fails without the annotation, but BOTH work if the annotation is added.