Spring Cloud Contract with Jersey - spring

I have a simple project Spring boot project. It contains one Jersey based Controller:
#Path("persons")
#Produces(MediaType.APPLICATION_JSON)
public class PersonsController {
#GET
public Person get() {
return new Person("James", 20);
}
}
It returns json response as expected (url: http://localhost:PORT/persons):
{
"name": "James",
"age": 20
}
My aim is to add Spring Cloud Contract tests for this controller. I have added all required mvn configurations, and test:
public class MvcTest {
#Before
public void setup() {
RestAssuredMockMvc.standaloneSetup(new PersonsController());
}
}
Here is contract (groovy file):
import org.springframework.cloud.contract.spec.Contract
Contract.make {
request {
method 'GET'
url('persons')
}
response {
status 200
body(
"name": "James",
"age": 20
)
}
}
When I run mvn clean package following error always is returned:
Failed tests:
ContractVerifierTest.validate_getTest:26 expected:<[200]> but was:<[404]>
I believe this should be related to the ServletDispatcher as it doesn't see Jersey's paths. The same project with replaced #Path to #RequestMapping works. However, I need to make it working with Jersey.
Have I missed something?

Have you checked the section about jaxrs support? https://cloud.spring.io/spring-cloud-contract/1.0.x/spring-cloud-contract.html#_jax_rs_support. Here you have an example how you can use it https://github.com/spring-cloud/spring-cloud-contract/tree/1.0.x/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject

Related

WebTestClient gets 404 on Spring Boot 2.4.0-M3 while works fine on 2.4.0-M2

I have test that works properly with Spring 2.4.0-M2 but after upgrading to 2.4.0-M3 it breaks - returns 404 for a route that is registered.
My app:
#SpringBootApplication(proxyBeanMethods = false)
class ExampleApp
fun main(args: Array<String>) {
runApplication<ExampleApp>(
init = {
addInitializers(BeansInitializer())
},
args = args
)
}
beans:
class BeansInitializer : ApplicationContextInitializer<GenericApplicationContext> {
#Suppress("LongMethod")
override fun initialize(applicationContext: GenericApplicationContext) {
beans {
bean {
router {
"/routes".nest {
GET("/{id}") { ServerResponse.ok().bodyValue(Foo("ok")) }
POST("/") { ServerResponse.ok().bodyValue(Foo("ok")) }
}
}
}
}
.initialize(applicationContext)
}
}
data class Foo(val status: String)
My test:
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = [
ExampleApp::class
]
)
class FailingTest #Autowired constructor(
context: ApplicationContext,
) {
val webTestClient: WebTestClient = WebTestClient.bindToApplicationContext(context)
.configureClient()
.build()
#Test
fun `should interact with routes`() {
webTestClient
.post()
.uri("/routes")
.bodyValue(SampleBody("123"))
.exchange()
.expectStatus()
.isOk // returns 404 on 2.4.0-M3 / passes on 2.4.0-M2
}
data class SampleBody(val id: String)
}
test application.yml
context:
initializer:
classes: com.example.BeansInitializer
On 2.4.0-M3 tests fail with following message:
java.lang.AssertionError: Status expected:<200 OK> but was:<404 NOT_FOUND>
On 2.4.0-M2 they pass.
Is there something that changed through the versions? Or this is a bug?
The change in behaviour that you are seeing is due to an improvement in Spring Framework during the development of 5.3.
By default, Spring Framework will match an optional trailing path separator (/). This optional / should be in addition to the path specified in your routes.
You have two routes:
GET /routes/{id}
POST /routes/
The support for an optional trailing path separator means that you could make a get request to /routes/56/ (an additional trailing /), but it should not mean that you can make a request to POST /routes (removal of a trailing /).
If you want to be able to make POST requests to both /routes and /routes/, you should define the route as /routes:
beans {
bean {
router {
"/routes".nest {
GET("/{id}") { ServerResponse.ok().bodyValue(Foo("ok")) }
POST("") { ServerResponse.ok().bodyValue(Foo("ok")) }
}
}
}
}

Loading json into the database during integration tests

I am trying to write some integration tests (Junit5) where I will need to fetch some data from a repository. I would like to pre-populate the database with some json data (I want to avoid sql) before the test start.
After some research I manage to create the test-database.json file inside my resource folder:
[
{
"_class": "com.marcellorvalle.scheduler.entity.Professional",
"id": 1,
"name": "James Holden"
},
{
"_class": "com.marcellorvalle.scheduler.entity.Professional",
"id": 2,
"name": "Gerald of Rivia"
},
{
"_class": "com.marcellorvalle.scheduler.entity.Professional",
"id": 3,
"name": "Christjen Avassalara"
}
]
And also the configuration file:
#Configuration
public class TestConfiguration {
#Bean
public Jackson2RepositoryPopulatorFactoryBean getRepositoryPopulator() {
final Jackson2RepositoryPopulatorFactoryBean factory = new Jackson2RepositoryPopulatorFactoryBean();
factory.setResources(new Resource[]{new ClassPathResource("test-database.json")});
return factory;
}
}
I have a dummy test just to check if everything is ok:
#DataJpaTest
#ExtendWith(SpringExtension.class)
public class IntegrationTest {
final private ProfessionalRepository repository;
#Autowired
public IntegrationTest(ProfessionalRepository professionalRepository) {
repository = professionalRepository;
}
#Test
public void testFindAll() {
final List<Professional> result = repository.findAll();
Assertions.assertEquals(3, result.size());
}
}
When I run the test it will fail with no results. Next I added the following to the TestClass:
#ContextConfiguration(classes=TestConfiguration.class)
Now the TestConfiguration is loaded but it can not resolve ProfessionalRepository:
org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [com.marcellorvalle.scheduler.repository.ProfessionalRepository professionalRepository] (...) Failed to load ApplicationContext
Is there any way to read the TestConfiguration without messing the application context?
Instead of using #ContextConfiguration that wipe all your default configs and replace them by the specified classes, try using #Import over your class. That will aggregate your config.
You should check out #ComponentScan("base-package-where-scan-starts'") annotation and put it at your TestConfiguration class
https://www.javarticles.com/2016/01/spring-componentscan-annotation-example.html

spring-test MockMvc kotlin DSL lacks async support?

spring-test has added support for a MockMvc DSL which can be found documented here:
https://docs.spring.io/spring/docs/5.2.0.M1/spring-framework-reference/languages.html#mockmvc-dsl
When testing a controller that returns a CompletableFuture (or any other async result type) a test using MockMvc needs to perform an asyncDispatch of the MvcResult before the body can be asserted.
This can be found on various blogs or stackoverflow questions:
https://sdqali.in/blog/2015/11/24/testing-async-responses-using-mockmvc/
MockMVC perform post test to async service
The new DSL seems to lack a clean way to do this.
For example the following code is needed to do the asyncDispatch:
#Test
internal fun call() {
val mvcResult = mockMvc.get("/execute") {
accept = APPLICATION_JSON
}.andExpect {
request { asyncStarted() }
}.andReturn()
mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.jsonPath("$.value", Is.`is`("test")))
}
Am I missing something that would enable this or is this just not nicely supported in the DSL (yet)?
Update:
I tried improving this with an extension function on ResultActionsDsl.
fun ResultActionsDsl.asyncDispatch(mockMvc: MockMvc):ResultActionsDsl {
val mvcResult = andReturn()
mockMvc.perform(MockMvcRequestBuilders.asyncDispatch(mvcResult))
return this
}
This makes it possible to write the test as:
#Test
internal fun call() {
mockMvc.get("/execute") {
accept = APPLICATION_JSON
}.andExpect {
request {
asyncStarted()
}
}
.asyncDispatch(mockMvc)
.andExpect {
status { isOk }
jsonPath("$.value") { value("test") }
}
}
I still feel like this would be supported out of the box by the DSL.
This will be supported as of Spring Framework 5.2.2 and Spring Boot 2.2.2 with the following syntax:
mockMvc.get("/async").asyncDispatch().andExpect {
status { isOk }
}
See related issue for more details.

Intellij different behavior depends on deployed war exploded or not

I see strange behavior in Intellij and JBoss server.
Short info about application:
- spring 4.3.1
- jersey 1.18.1
- jboss EAP 6.4.0
I have an issue with marshaling, so I tried to dig deeper and try hints from: https://docs.jboss.org/resteasy/docs/1.0.0.GA/userguide/html/Content_Marshalling_Providers.html
So I created class:
#Named
#Provider
#Primary
#Produces(MediaType.APPLICATION_JSON)
public class JerseyJAXBConfiguration implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class[] types = { SomeClass.class};
public JerseyJAXBConfiguration() throws Exception {
JSONConfiguration.MappedBuilder builder = JSONConfiguration.mapped();
builder.arrays("invite");
builder.rootUnwrapping(false);
this.context = new JSONJAXBContext(builder.build(), types);
}
public JAXBContext getContext(Class<?> objectType) {
for (Class type : types) {
if (type == objectType) {
return context;
}
}
return null;
}
}
And I have two JBoss run configurations in IntelliJ. First that deploy simple 'war' and second that deploy 'war exploded'. And here is a strange thing. Because in both cases this bean is loaded when the application is starting, but only for a case without exploded 'getContext' method is executed. In both cases, everything is the same. Nothing in code is changed between runs.
Can someone explain to me why we have this different? I would like to know what JAXB implementation is executed in a case for 'war exploded'.
The main reason why I looking at it is because Json that is created by web service also is different in metioned cases. On have "named" array second don't have.
In case 'war':
{"functionGroupAutoFill": [
{
"desc": "0. General",
"id": "0"
},
{
"desc": "00. General",
"id": "00"
},
...
]}
In case 'war exploded':
[
{
"desc": "0. General",
"id": "0"
},
{
"desc": "00. General",
"id": "00"
},
...
]

How to remove the "_embedded" property in Spring HATEOAS

I'm using Spring Boot and HATEOAS to build a REST API and when my API returns a collection, it is wrapped inside a "_embedded" property, like so:
{
"_links":{
"self":{
"href":"http://localhost:8080/technologies"
}
},
"_embedded":{
"technologies":[
{
"id":1,
"description":"A",
"_links":{
"self":{
"href":"http://localhost:8080/technologies/1"
}
}
},
{
"id":2,
"description":"B",
"_links":{
"self":{
"href":"http://localhost:8080/technologies/2"
}
}
}
]
}
}
I want the response to be like this:
{
"_links":{
"self":{
"href":"http://localhost:8080/technologies"
}
},
"technologies":[
{
"id":1,
"description":"A",
"_links":{
"self":{
"href":"http://localhost:8080/technologies/1"
}
}
},
{
"id":2,
"description":"B",
"_links":{
"self":{
"href":"http://localhost:8080/technologies/2"
}
}
}
]
}
My TechnologiesController:
#RestController
#ExposesResourceFor(Technology.class)
#RequestMapping(value = "/technologies")
public class TechnologiesController {
...
#ResquestMapping(method = RequestMethod.GET, produces = "application/vnd.xpto-technologies.text+json")
public Resources<Resource<Technology>> getAllTechnologies() {
List<Technology> technologies = technologyGateway.getAllTechnologies();
Resources<<Resource<Technology>> resources = new Resources<Resource<Technology>>(technologyResourceAssembler.toResources(technologies));
resources.add(linkTo(methodOn(TechnologiesController.class).getAllTechnologies()).withSelfRel());
return resources;
}
The configuration class has the annotation #EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL).
What is the best way to produce the response without the "_embedded"?
As the documentation says
application/hal+json responses should be sent to requests that accept
application/json
In order to omit _embedded in you response you'll need to add
spring.hateoas.use-hal-as-default-json-media-type=false
to application.properties.
I close HAL feature, because it is hard to using Resources/Resource by restTemplate. I disable this feature by following code:
public class SpringRestConfiguration implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setDefaultMediaType(MediaType.APPLICATION_JSON);
config.useHalAsDefaultJsonMediaType(false);
}
}
It work for me. HAL is good if there are more support with restTemplate.
Adding this Accept header to the request:
Accept : application/x-spring-data-verbose+json
For those who use Spring Data, and consider it as a problem - solution is to set
spring.data.rest.defaultMediaType = application/json
in application properties.
There still links will be available, but no _embedded any more.
What you're describing in the produced and expected results are semantically different things. The former thing is the HAL representation of a Collection<Technology>. The latter, which you expect is the representation of:
class Wrapper {
Resources<Technology> technologies;
}
Note how this is how we actually create the top level technologies property that you would like to see in your response. You don't create any of the latter in your controller. A top-level Resourcesinstance is basically a collection and the only way to represent a top-level collection in HAL is _embedded. Apparently you don't want that but that's what you have written in your controller method.
Assuming you have Wrapper, something like this should work (untested):
Wrapper wrapper = new Wrapper(assembler.toCollectionModel(technologies);
EntityModel<Wrapper> model = EntityModel.of(wrapper);
model.add(linkTo(…));
PS: As of Spring HATEOAS 1.0, Resources is CollectionModel and Resourceis EntityModel.
You can use this code in the service
constructor(
private httpClient: HttpClient
) { }
retrieveAllStudents(){
return this.httpClient.get<any[]>(`http://localhost:8080/students`);
}
This will deal with the _embedded part of Json and extract the desired data.
export class ListStudentsComponent implements OnInit {
// declaring variables to be used
student: Student;
students: Student[];
message: string;
// injecting student service into the constuctor
constructor(
private studentService: StudentService,
) { }
ngOnInit() {
this.refreshStudents();
}
refreshStudents(){
this.studentService.retrieveAllStudents().subscribe(
response => {
console.log(response);
this.students = response._embedded.students as Student[];
}
);
}
For latest versions in Spring RepositoryRestConfigurer doesn't include the method public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) you'd need to override the default method on RepositoryRestConfigurer which include cors parameter.
public class RestConfiguration implements RepositoryRestConfigurer {
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
config.setDefaultMediaType(MediaType.APPLICATION_JSON);
config.useHalAsDefaultJsonMediaType(false);
}
}

Resources