Search code examples
spring-bootspring-webfluxopenapi-generator

WebFlux API-Layer Test returns 404


I'm trying to get started with Spring WebFlux with Spring Boot 3.0

I'm Building a Person API with an open api generator. The Application runs and gives the expected results when it is tested manually.

But I'm not able to get the API layer unit tested.

This is my Test Class

@WebFluxTest(controllers = {PersonApiController.class})
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {PersonMapperImpl.class, H2PersonRepository.class, PersonRepository.class})
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class PersonRouterTest {

    @MockBean
    private PersonService personService;

    @Autowired
    private WebTestClient client;

    @ParameterizedTest
    @CsvSource({"1234, Max Mustermann", "5678, Erika Musterfrau"})
    void retrieve_a_name(String id, String name) {
        when(personService.getPersonDataByID(1234)).thenReturn(Mono.just(new PersonData(1234, "Max Mustermann")));
        when(personService.getPersonDataByID(5678)).thenReturn(Mono.just(new PersonData(5678, "Erika Musterfrau")));

        client.get()
                .uri(uriBuilder -> uriBuilder
                        .path("/persons/{id}")
                        .build(id))
                .accept(MediaType.ALL)
                .exchange()
                .expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBody()
                .jsonPath("$.name").isEqualTo(name);
    }

This is my Controller Class

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-12-

09T09:14:36.692713900+01:00[Europe/Vienna]")
@Controller
@RequestMapping("${openapi.openAPIDefinition.base-path:}")
public class PersonApiController implements PersonApi {

    private final PersonApiDelegate delegate;

    public PersonApiController(@Autowired(required = false) PersonApiDelegate delegate) {
        this.delegate = Optional.ofNullable(delegate).orElse(new PersonApiDelegate() {});
    }

    @Override
    public PersonApiDelegate getDelegate() {
        return delegate;
    }

}

The API interface:

@Tag(
    name = "Person",
    description = "the Person API"
)
public interface PersonApi {
    default PersonApiDelegate getDelegate() {
        return new PersonApiDelegate() {
        };
    }

    @Operation(
        operationId = "findPersonById",
        summary = "Find Person by ID",
        tags = {"Person"},
        responses = {@ApiResponse(
    responseCode = "200",
    description = "successful operation",
    content = {@Content(
    mediaType = "application/json",
    schema = @Schema(
    implementation = PersonData.class
)
)}
)}
    )
    @RequestMapping(
        method = {RequestMethod.GET},
        value = {"/persons/{id}"},
        produces = {"application/json"}
    )
    default Mono<ResponseEntity<PersonData>> findPersonById(@Parameter(name = "id",description = "Person ID",required = true) @PathVariable("id") Integer id, @Parameter(hidden = true) final ServerWebExchange exchange) {
        return this.getDelegate().findPersonById(id, exchange);
    }

    @Operation(
        operationId = "savePerson",
        summary = "Creates a new Person",
        tags = {"Person"},
        responses = {@ApiResponse(
    responseCode = "200",
    description = "successful operatoin",
    content = {@Content(
    mediaType = "application/json",
    schema = @Schema(
    implementation = PersonData.class
)
)}
)}
    )
    @RequestMapping(
        method = {RequestMethod.POST},
        value = {"/persons"},
        produces = {"application/json"},
        consumes = {"application/json"}
    )
    default Mono<ResponseEntity<PersonData>> savePerson(@Parameter(name = "PersonData",description = "") @RequestBody(required = false) Mono<PersonData> personData, @Parameter(hidden = true) final ServerWebExchange exchange) {
        return this.getDelegate().savePerson(personData, exchange);
    }
}

and finally my delegate impl:

@Service
public class PersonDelegateImpl implements PersonApiDelegate {

    public static final Mono<ResponseEntity<?>> RESPONSE_ENTITY_MONO = Mono.just(ResponseEntity.notFound().build());

    private final PersonService service;
    private final PersonMapper mapper;

    public PersonDelegateImpl(PersonService service, PersonMapper mapper) {
        this.service = service;
        this.mapper = mapper;
    }

    public static <T> Mono<ResponseEntity<T>> toResponseEntity(Mono<T> mono) {
        return mono.flatMap(t -> Mono.just(ResponseEntity.ok(t)))
                .onErrorResume(t -> Mono.just(ResponseEntity.internalServerError().build()));

    }

    @Override
    public Mono<ResponseEntity<PersonData>> findPersonById(Integer id, ServerWebExchange exchange) {
        Mono<com.ebcont.talenttoolbackend.person.PersonData> personDataByID = service.getPersonDataByID(id);
        return toResponseEntity(personDataByID.map(mapper::map));
    }

    @Override
    public Mono<ResponseEntity<PersonData>> savePerson(Mono<PersonData> personData, ServerWebExchange exchange) {
        return PersonApiDelegate.super.savePerson(personData, exchange);

If I run the test class I always get:

< 404 NOT_FOUND Not Found
< Content-Type: [application/json]
< Content-Length: [139]

{"timestamp":"2022-12-09T08:45:41.278+00:00","path":"/persons/1234","status":404,"error":"Not Found","message":null,"requestId":"4805b8b8"}

I have tried to change the Context Configuration but I did not get it to work.


Solution

  • I found the Problem, changing the Test Config to :

    @WebFluxTest
    @ExtendWith(SpringExtension.class)
    @ContextConfiguration(classes = {PersonMapperImpl.class, H2PersonRepository.class, PersonRepository.class, PersonApiController.class, PersonDelegateImpl.class})
    @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
    

    Solved my Problem. The Controller bean was not recognized. I had to add PersonApiCrontroller and PersonDelegateImpl to the Context Config. i then removed the PersonApiController from the @WebFluxTest annotation.