Reputation: 5071
How can I unit test this code?
private ODataQueryResult buildAndExecuteQuery(String path String entity,
String sapClient, String sapLanguage) {
ODataQuery query = ODataQueryBuilder
.withEntity(path, entity)
.withHeader("sap-client", sapClient, true)
.withHeader("sap-language", sapLanguage, true)
.withoutMetadata()
.build();
return query.execute();
}
More precisely: How can I verify that my code calls all the right functions, for example does not forget to call withoutMetadata
or set the required headers?
Unfortunately, ODataQueryBuilder
and ODataQuery
are classes, not interfaces. which makes mocking tricky. ODataQueryBuilder
is even final
, disabling mocking completely. Also, the chain starts with a static method withEntity
which can also not be mocked.
Are there helpers that allow me to spy on behavior or mock data, similar to the MockUtil
described in https://blogs.sap.com/2017/09/19/step-12-with-sap-s4hana-cloud-sdk-automated-testing/?
Upvotes: 0
Views: 783
Reputation: 5071
Partial solution is to simulate executing the ODataQuery on a mock HttpClient.
First, the original method needs to be taken apart into two independent parts, one for building the query, the other for executing it. This is good design anyway, so no big problem:
private ODataQuery buildQuery(String path, String entity,
String sapClient, String sapLanguage) {
return ODataQueryBuilder
.withEntity(path, entity)
.withHeader("sap-client", sapClient, true)
.withHeader("sap-language", sapLanguage, true)
.withoutMetadata()
.build();
}
private ODataResponse executeQuery(ODataQuery query) {
return query.execute();
}
The buildQuery method can now be tested as follows:
@Test
public void addsSapLanguageToHeader() throws ODataException, IOException {
ODataQuery query = cut.buildQuery("api/v2", "business-partners", "", "fr");
HttpUriRequest request = getRequest(query);
assertContainsHeader(request, "sap-language", "fr");
}
The method getRequest produces a fake HttpClient that stubs all methods required to get query.execute(httpClient)
to work. It stores the actual request and returns it for further inspection. Sample implementation with Mockito:
private HttpUriRequest getRequest(ODataQuery query) throws ODataException, IOException {
// stub methods to make code work
HttpResponse response = mock(HttpResponse.class);
when(httpClient.execute(any())).thenReturn(response);
StatusLine statusLine = mock(StatusLine.class);
when(response.getStatusLine()).thenReturn(statusLine);
HttpEntity entity = mock(HttpEntity.class);
when(response.getEntity()).thenReturn(entity);
InputStream inputStream = new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8));
when(entity.getContent()).thenReturn(inputStream);
Header[] headers = new Header[0];
when(response.getAllHeaders()).thenReturn(headers);
// simulate the execution of the query
query.execute(httpClient);
// grab the original argument from the mock for inspection
ArgumentCaptor<HttpUriRequest> captor = ArgumentCaptor.forClass(HttpUriRequest.class);
verify(httpClient).execute(captor.capture());
HttpUriRequest request = captor.getValue();
return request;
}
This solution is far from perfect, of course.
First, the amount of code needed to make this work alone shows how fragile this test will be over time. Whenever the CloudSDK decides to add a method or validation to the call sequence, this test will break. Note also that the test is invasive, by testing a private
method, while gold standards say we should test only public
methods.
Second, the method executeQuery can still not be tested. Execution paths also differ, because the test code uses the .execute(httpClient)
variant to run the query, while the original code uses the .execute(destinationName)
variant. The two happen to share code, but this may change over time.
Upvotes: 0
Reputation: 1124
You are right, the structure of those classes make them hard to mock, but as they are part of the "SAP Cloud Platform SDK for Service Development" we have no way to change them.
Other approaches might be:
If you also would see an integration test suiting you could consider using http://wiremock.org/docs/getting-started/. With that you would be able to setup a "Mock Server", preparing responses for defined requests and with that verify the content of any HTTP call made by your test.
We use WireMock in the SAP Cloud SDK and also provide some integration into our SDK via the MockUtil
contained in our testutil-core
module.
I hope this helps a bit!
Upvotes: 2