Spring HATEOAS / MockMvc / JsonPath best practices - spring

I'm writing unit tests for Spring HATEOAS backend using MockMvc and JsonPath.
To test the links contained in a response I'm doing something like:
#Test
public void testListEmpty() throws Exception {
mockMvc.perform(get("/rest/customers"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.links", hasSize(1))) // make sure links only contains self link
.andExpect(jsonPath("$.links[?(#.rel=='self')]", hasSize(1))) // make sure the self link exists 1 time
.andExpect(jsonPath("$.links[?(#.rel=='self')].href", contains("http://localhost/rest/customers{?page,size,sort}"))) // test self link is correct
.andExpect(jsonPath("$.links[?(#.rel=='self')][0].href", is("http://localhost/rest/customers{?page,size,sort}"))) // alternative to test self link is correct
.andExpect(jsonPath("$.content", hasSize(0))); // make sure no content elements exists
}
However I wonder if there are some best practices I should use to make it easier for myself like:
Testing link contains http://localhost does not feel right. Can I use some Spring MovkMvc helper to determine the host?
With JsonPath it's difficult to test if an array contains an element where 2 attributes have certain value.
Like that the array should contain a self link with a certain value.
Is there a better way to test that then above
This will also come into play when testing validation errors for fields with error messages.
I've see technique like below in some blog posts:
.andExpect(jsonPath("$.fieldErrors[*].path", containsInAnyOrder("title", "description")))
.andExpect(jsonPath("$.fieldErrors[*].message", containsInAnyOrder(
"The maximum length of the description is 500 characters.",
"The maximum length of the title is 100 characters.")));
But this does not guarantee at all that title has the specific error message.
It could also be that the title has incorrectly the "The maximum length of the description is 500 characters." but the test will succeed.

You may use Traverson (included in Spring HATEOAS) to traverse the links in tests.
If you are using Spring Boot, I'd consider using #WebIntegrationTest("server.port=0") rather than MockMvc, as in some cases I experienced behaviours slightly different from the actual application.
You may find some example in a post of mine: Implementing HAL hypermedia REST API using Spring HATEOAS.
Also look at the tests in the sample project.

One approach that addresses the http://localhost concern without sacrificing the need to test two attribute constraints on an array element is to use the org.hamcrest.CoreMatchers.hasItem(org.hamcrest.Matcher nestedMatcher) matcher. The test you showed above now becomes:
.andExpect(jsonPath("$.links[?(#.rel=='self')].href", hasItem(endsWith("/rest/customers{?page,size,sort}"))))
.andExpect(jsonPath("$.links[?(#.rel=='self')][0].href", hasItem(endsWith("/rest/customers{?page,size,sort}"))))

Related

Trouble faking a Laravel HttpClient response

I am trying to test the following bit of code:
DimonaClient is just a simple wrapper around a Laravel HttpClient; simplified function here:
The getDeclaration() response is a \Illuminate\Http\Client\Response
What I am trying to do in my test is:
Mock the DimonaClient class so I don't create an actual api call
"Mock" (use Laravel's Http::response()) the response I want so that I can test that a 200 w/ certain statuses dispatches the appropriate event (also mocked, but not relevant here)
My test code looks like this:
My issue(s) seem to be:
the getDeclaration() has an expectation of Illuminate\Http\Client\Response but I can't seem to create anything that will satisfy that (a new Response wants a MessageInterface, etc, etc... )
I don't actually need getDeclaration() to return anything for my testing, so I wonder if I should be mocking this differently in any case (I base this assumption on Http::response handling the internal code I'm testing for things like $response->ok(), instead of a Mockery expectation)
I feel like I'm one small step away from making this work, but going round in circles trying to hook it up correctly.
TIA!
If you are using Http Facade, you don't need to mock DimonaCient. You are nearly there with your test, but let me show you what you would have done:
/** #test */
public function it_can_handle_an_approved_submission(): void
{
Http::fake([
'*' => Http::response([
'declarationStatus' => [
'result' => DimonaDeclarationStatus::ACCEPTED,
'dimonaPeriodId' => $this->faker->numerify('############'),
],
],
]);
$dimonaDeclarationId = $this->faker->numerify('############');
// Do your normal call, and then assertions
}
Doing this, you will tell Http to fake any URL, because we are using *. I would recommend you use $this->endpoint/$declarationId so if it does not match, you will also know you did not hit the right endpoint.
I am not sure what Laravel you are using but this is available since Laravel 6+, check Http fake URLs.

Rest camel passing objects between endpoints

Overview.
My camel setup calls two service methods. the response of the first one is passed into the second one and then output the final response as json webpage. Fairly simple nothing too complicated.
Further breakdown to give some more context.
Method_1. Takes in scanId. This works ok. It produces an object called ScheduledScan .class
Method_2. Takes in object previous instance of ScheduledScan .class and returns a list of ConvertedScans scans. Then would like to display said list
Description of the code
#Override
public void configure() throws Exception {
restConfiguration().bindingMode(RestBindingMode.json);
rest("/publish")
.get("/scheduled-scan/{scanId}")
.to("bean:SentinelImportService?method=getScheduledScan").outType(ScheduledScan .class)
.to("bean:SentinelImportService?method=convertScheduledScan");
}
The methods that are called look like the following
ScheduledScan getScheduledScan(#Header("scanId") long scanId);
List<ConvertedScans > convertScheduledScan(#Body ScheduledScan scheduledScans);
It is returning the the following error
No body available of type: path. .ScheduledScan but has value:
of type: java.lang.String on: HttpMessage#0x63c2fd04. Caused by: No type converter available
The following runs without the error, i.e. without method 2. So I think im almost there.
rest("/publish")
.get("/scheduled-scan/{scanId}")
.to("bean:SentinelImportService?method=getScheduledScan");
Now from reading the error it looks like im passing in a HttpMessage not the java object? I'm a bit confused about what to do next? Any advice much appreciated.
I have found some similar questions to this message. However I am looking to pass the java object directly into the service method.
camel-rest-bean-chaining
how-to-share-an-object-between-methods-on-different-camel-routes
You should setup the outType as the last output, eg what the REST response is, that is a List/Array and not a single pojo. So use .outTypeList(ConvertedScans.class) instead.

Generating PHPUnit tests from array with endpoints

For an application we use configuration files in which a large number of endpoint characteristics are determined (relations, fillables, visibles, roles, etc.) We would like to loop through these files and conduct automatic tests with PHPUnit, simply to see if we receive a response, if validation errors are being triggered, if the response is in line with the files, etc.
We load the configuration and perform the tests for each endpoint configuration:
public function testConfigurationFiles()
{
$config = resolve('App\Contracts\ConfigInterface');
foreach ($config->resources as $resource=>$configuration) {
foreach ($configuration->endpoints() as $method=>$rules) {
$this->endpoint($method, $resource, $configuration);
}
}
}
After which we use a switch, to test each type of method differently (index, show, create, update, delete). In total this comes down to dozens of tests with hundreds of assertions.
However, if even one of these endpoints fails, the entire tests fails without showing explicit information what went wrong. Is there a way to automatically generate a "test{$resource}{$method}" method for each endpoint, so they will be handled like individual tests?
Besides these tests we also conduct units tests & e2e tests, so we are fully aware of the disadvantages of this way of testing.
After studying PHPUnit some more, I found my answer in dataProviders:
https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers
This way you can indicate a data provider for a method, which should return an array with all cases you want to iterate over.

How do I validate responses with Gauge (getgauge.io)?

I've looked in the documentation and sample C# project: http://getgauge.io/documentation/user/current/
However, I'm not sure how to validate the response from a "Step". Maybe Gauge isn't the right tool for this but I'm trying to validate the format of a JSON response for instance.
On their standard "StepImplementation" class, I can see the following method. I added 'return "blah";' to the end:
[Step("Say <what> to <who>")]
public string SaySomething(string what, string who)
{
Console.WriteLine("{0}, {1}!", what, who);
return "blah";
}
And of course in the spec file:
First scenario
--------------
tags: hello world, first test
* Say "hello" to "gauge"
* Check if "blah" is returned from SaySomething
It fails on that last line because the Step isn't defined (duh). But, what I really want is something like - Say "hello" to "gauge" and expect "blah".
Yeah... Apparently I wasn't fully understanding the power of Gauge and what it is designed to do. The answer to my silly question is:
Use your favorite testing framework and use Asserts. Those Assert failures will show up on the Gauge reports (even though those Asserts aren't actually coming FROM Gauge - errors will fail the Scenario/Step too). Whether you are using C# or Java, just assume that Gauge is your test runner I guess, and you can do whatever you want for validation.

Oracle Service Bus - Assign expression

I have this problem and I am not sure why it's happening and how to fix it. I have created an OSB peject. In the proxy service pipeline I am doing a Service Callout to a sync SOAP service in another application. The other service needs the request body as below:
<RequestSelectionValues xmlns="http://www.camstar.com/WebService/WSShopFloor">
<inputServiceData xmlns:q1="http://www.camstar.com/WebService/DataTypes" q1:type="OnlineQuery">
<OnlineQuerySetup>
<__CDOTypeName/>
<__name>xLot By FabLotNumber</__name>
</OnlineQuerySetup>
<Parameters>
<__listItem>
<Name>FabLotNumber</Name>
<DefaultValue>FAB_Lot_1</DefaultValue>
</__listItem>
<__listItem>
<Name>BLOCKOF200ROWS</Name>
<DefaultValue>1</DefaultValue>
</__listItem>
</Parameters>
</inputServiceData>
<queryOption xmlns:q2="http://www.camstar.com/WebService/DataTypes" q2:type="QueryOption">
<RowSetSize>1000</RowSetSize>
<StartRow>1</StartRow>
<QueryType>user</QueryType>
<ChangeCount>0</ChangeCount>
<RequestRecordCount>false</RequestRecordCount>
<RequestRecordSetAndCount>false</RequestRecordSetAndCount>
</queryOption>
<serviceInfo xmlns:q3="http://www.camstar.com/WebService/DataTypes" q3:type="OnlineQuery_Info">
<OnlineQuerySelection>
<RequestValue>false</RequestValue>
<RequestMetadata>false</RequestMetadata>
<RequestSubFieldValues>false</RequestSubFieldValues>
<RequestSelectionValues>true</RequestSelectionValues>
</OnlineQuerySelection>
</serviceInfo>
</RequestSelectionValues>
I am using an Assign to put the above expression in a variable.
Notice the line:
<serviceInfo xmlns:q3="http://www.camstar.com/WebService/DataTypes" q3:type="OnlineQuery_Info">
xmlns:q3="http://www.camstar.com/WebService/DataTypes" needs to be before q3:type="OnlineQuery_Info" for the other service to be called successfully otherwise the service call fails.
In the development it looks fine. I can test the assign of expression as well.
When I go to the OSB console to test the service I notice that in the Assign variable the namespace place switches and it becomes like this:
<serviceInfo q3:type="OnlineQuery_Info" xmlns:q3="http://www.camstar.com/WebService/DataTypes">
This makes the service calls to fail. I have tried putting the body payload in an xslt. Result is the same. I am not sure why it switches the type before namespace. The end result is that the service is not working as expected.
Any idea what I can do to fix this issue. How can I prevent the switching?
Thanks
I haven't found any settings in OSB that can prevent reordering of attributes for you. However, the above OSB behavior is completely XML standard compliant. In fact, the target service side should be XML compliant and treat the two variants mentioned above as the same, because according to XML standard, tow XML documents with only difference in attribute ordering should be treated as the same.
EDIT:
Please go here to download a modified config. My thoughts are:
Specify the business service to invoke in 'Text as Request' mode, as "CamstarLotQuery/business/CSWSShopFloor_Txt" shown below:
Manipulate messages as text, not XML, in your proxy service, as specified in "CamstarLotQuery/proxy/CamstarLotQueryTxt_Txt":
You might need to specify a SOAP Action in http header when calling a business service, depending on the target service.
One solution i can think of is to assign all the namespaces at the Parent Tag Level, and keep the attributes where they are applicable.
Example:
<RequestSelectionValues xmlns:q1="http://www.camstar.com/WebService/DataTypes" xmlns="http://www.camstar.com/WebService/WSShopFloor" xmlns:q2="http://www.camstar.com/WebService/DataTypes" xmlns:q3="http://www.camstar.com/WebService/DataTypes">
But the problem with this implementation is that since the namespace declaration is now Global, you have to declare your namespace prefixes (q1, q2, q3) to the blocks where the namespaces were previously defined.
Example:
<q3:serviceInfo q3:type="OnlineQuery_Info">
<q3:OnlineQuerySelection>
<q3:RequestValue>false</q3:RequestValue>
<q3:RequestMetadata>false</q3:RequestMetadata>
<q3:RequestSubFieldValues>false</q3:RequestSubFieldValues>
<q3:RequestSelectionValues>true</q3:RequestSelectionValues>
</q3:OnlineQuerySelection>
</q3:serviceInfo>
if this namespace prefix is not declared, then as per XML standards, the tag assume the 'default' namespace value - which will be the namespace of the parent.
However, even though this solution has a round-about way of implementation, this solution will definitely work.

Resources