#1225 - Provide implementation of WebTestClientConfigurer to support hypermedia registration.

Expand reference documentation to show ways to use it directly or via Spring Boot.
This commit is contained in:
Greg Turnquist
2020-03-27 16:52:51 -05:00
parent 6c09839cca
commit 898752f0d5
6 changed files with 296 additions and 13 deletions

View File

@@ -15,14 +15,6 @@
*/
package org.springframework.hateoas;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*;
import static org.springframework.hateoas.ArchitectureTest.Architecture.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.springframework.lang.Nullable;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.Dependency;
import com.tngtech.archunit.core.domain.JavaClass;
@@ -33,6 +25,13 @@ import com.tngtech.archunit.core.importer.ImportOptions;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import com.tngtech.archunit.library.dependencies.SliceRule;
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.springframework.lang.Nullable;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*;
import static org.springframework.hateoas.ArchitectureTest.Architecture.*;
/**
* Tests to verify certain architectural assumptions.
@@ -69,6 +68,7 @@ class ArchitectureTest {
.and().resideOutsideOfPackages("..reactive") //
.and().haveSimpleNameNotStartingWith("WebFlux") //
.and().haveSimpleNameNotStartingWith("WebClient") //
.and().haveSimpleNameNotStartingWith("HypermediaWebTestClient") //
.should().dependOnClassesThat(areSpringFrameworkClassesWithReactiveDependency) //
.check(classes);
}

View File

@@ -0,0 +1,145 @@
package org.springframework.hateoas.config;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.server.core.TypeReferences;
import org.springframework.hateoas.support.Employee;
import org.springframework.hateoas.support.WebFluxEmployeeController;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import java.util.Collections;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.hateoas.MediaTypes.*;
import static org.springframework.hateoas.support.ContextTester.*;
public class HypermediaWebTestClientConfigurerTest {
private static MediaType FRODO_JSON = MediaType.parseMediaType("application/frodo+json");
@Test // #1225
void webTestClientConfigurerHandlesSingleHypermediaType() {
withContext(HalConfig.class, context -> {
HypermediaWebTestClientConfigurer configurer = context.getBean(HypermediaWebTestClientConfigurer.class);
WebTestClient webTestClient = WebTestClient.bindToServer().apply(configurer).build();
assertThat(exchangeStrategies(webTestClient).messageReaders())
.flatExtracting(HttpMessageReader::getReadableMediaTypes) //
.contains(HAL_JSON) //
.doesNotContain(HAL_FORMS_JSON, COLLECTION_JSON, UBER_JSON);
});
}
@Test // #1225
void webTestClientConfigurerHandlesAllHypermediaTypes() {
withContext(AllHypermediaConfig.class, context -> {
HypermediaWebTestClientConfigurer configurer = context.getBean(HypermediaWebTestClientConfigurer.class);
WebTestClient webTestClient = WebTestClient.bindToServer().apply(configurer).build();
assertThat(exchangeStrategies(webTestClient).messageReaders())
.flatExtracting(HttpMessageReader::getReadableMediaTypes) //
.contains(HAL_JSON, HAL_FORMS_JSON, COLLECTION_JSON, UBER_JSON);
});
}
@Test // #1225
void webTestClientConfigurerHandlesCustomHypermediaTypes() {
withContext(CustomHypermediaConfig.class, context -> {
HypermediaWebTestClientConfigurer configurer = context.getBean(HypermediaWebTestClientConfigurer.class);
WebTestClient webTestClient = WebTestClient.bindToServer().apply(configurer).build();
assertThat(exchangeStrategies(webTestClient).messageReaders())
.flatExtracting(HttpMessageReader::getReadableMediaTypes) //
.contains(HAL_JSON, FRODO_JSON) //
.doesNotContain(HAL_FORMS_JSON, COLLECTION_JSON, UBER_JSON);
});
}
// tag::web-test-client[]
@Test // #1225
void webTestClientShouldSupportHypermediaDeserialization() {
// Configure an application context programmatically.
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(HalConfig.class); // <1>
context.refresh();
// Create an instance of a controller for testing
WebFluxEmployeeController controller = new WebFluxEmployeeController();
controller.reset();
// Extract the WebTestClientConfigurer from the app context.
HypermediaWebTestClientConfigurer configurer = context.getBean(HypermediaWebTestClientConfigurer.class);
// Create a WebTestClient by binding to the controller and applying the hypermedia configurer.
WebTestClient client = WebTestClient.bindToController(controller).apply(configurer).build(); // <2>
// Exercise the controller.
client.get().uri("http://localhost/employees") //
.exchange() //
.expectStatus().isOk() //
.expectBody(new TypeReferences.CollectionModelType<EntityModel<Employee>>() {}) // <3>
.consumeWith(result -> {
CollectionModel<EntityModel<Employee>> model = result.getResponseBody(); // <4>
// Assert against the hypermedia model.
assertThat(model.getRequiredLink(IanaLinkRelations.SELF)).isEqualTo(Link.of("/employees"));
assertThat(model.getContent()).hasSize(2);
});
}
// end::web-test-client[]
/**
* Extract the {@link ExchangeStrategies} from a {@link WebTestClient} to assert it has the proper message readers and
* writers.
*
* @param webTestClient
* @return
*/
private static ExchangeStrategies exchangeStrategies(WebTestClient webTestClient) {
WebClient webClient = (WebClient) ReflectionTestUtils.getField(webTestClient, "webClient");
return (ExchangeStrategies) ReflectionTestUtils
.getField(ReflectionTestUtils.getField(webClient, "exchangeFunction"), "strategies");
}
@EnableHypermediaSupport(type = HypermediaType.HAL)
static class HalConfig {
}
@EnableHypermediaSupport(
type = { HypermediaType.HAL, HypermediaType.HAL_FORMS, HypermediaType.COLLECTION_JSON, HypermediaType.UBER })
static class AllHypermediaConfig {
}
static class CustomHypermediaConfig extends HalConfig {
@Bean
HypermediaMappingInformation hypermediaMappingInformation() {
return () -> Collections.singletonList(FRODO_JSON);
}
}
}