Skip to content
Snippets Groups Projects
Commit 73fed49c authored by Gerd Aschemann's avatar Gerd Aschemann :speech_balloon:
Browse files

Add some documentation for BDD/Cucumber

parent 4c747550
No related branches found
No related tags found
1 merge request!235Draft: Cucumber implementation of first test steps
= BDD with Cucumber
:toc: left
:icons: font
== Introduction
Starting with https://git.fitko.de/fit-connect/planning/-/issues/654[Epic-654] (Bidirektionale Kommunikation in .NET- und Java-SDKs), we tried to define formal acceptance criteria with https://cucumber.io/docs/gherkin/[Gherkin].
Gherkin is a so-called BDD language (Behaviour Driven Design), i.e., it allows to specify behaviour using a semiformal approach of mixing keywords with English (or other) language fragments.
Example:
[source, gherkin]
.Gherkin example (first Acceptance criterion)
include::integration-tests/src/test/resources/features/test.feature[tag=ak1-1-1-optional-callback-url-is-used-upon-new-subscription]
Each statement can then be implemented as a so-called test step in Java (or other languages) using frameworks like https://cucumber.io/[Cucumber] (for Java), or https://specflow.org/[SpecFlow] (for .NET/C#).
The implementation of a step might look like this:
[[fig:checkWhetherTheSubmissionContainedTheSecret]]
[source, java]
.Java implementation of a particular test step
include::integration-tests/src/test/java/MyStepdefs.java[tag=theSubmissionContainsTheSecret]
Here we make sure that the submitted metadata (i.e., the payload to a put request) contains the desired _reply channel_.
We do not test getters/setters of Java objects, but the logic of the SDK itself, without making a call to the service itself (using some mock objects).
== Test flow
=== _Normal_ Integration Tests
The integration tests implemented so far, always perform a round trip from the test suite via the SDK to the submission service.
Therefore, they can only indirectly check whether the implemented calls had correct payloads.
[plantuml, normal-integration-tests, svg]
....
@startuml
control test
control sdk
control "submission-service" as zustelldienst
activate test
test -> sdk : Method call (Java)
activate sdk
sdk -> zustelldienst : REST API call(s) with suitable payload
activate zustelldienst
note over zustelldienst #red
We do not know whether the
desired parameters arrived
at the service?
endnote
return
return
note over test #lightgreen
We check the result here
and if it is positive,
we can assume the call hierarchy
contained the right data.
endnote
@enduml
....
=== _BDD_ Integration Tests
The BDD based tests should not require a running submission service.
Nevertheless, we would like to test sent data by intercepting the backend calls with mock objects.
Then it is possible to test the behaviour of the SDK itself by validating the HTTP payload which was handed over to the HTTP Client.
[plantuml, bdd-integration-tests, svg]
....
@startuml
control test
control sdk
control "httpClient\n(with submission-mock(s))" as zustelldienst
activate test
test -> sdk : Method call (Java)
activate sdk
sdk -> zustelldienst : Method call (Java)
activate zustelldienst
return
return
test -> zustelldienst : Check Contents
note over test #lightgreen
We can check the transmitted
parameters/data here!
endnote
@enduml
....
An implementation of such a mock interception looks like this:
[source, java]
----
include::integration-tests/src/test/java/MyStepdefs.java[tag=mockCreateSubmissionCall,indent=0]
----
<1> When the post request to create a new submission is executed
<2> We store the `create` object to verify later whether it contained the correct data, cf. <<fig:checkWhetherTheSubmissionContainedTheSecret>>
== Caveats
The current implementation contains two challenges with the used version of PowerMock which need bo be addressed in the future.
* PowerMock should support mocking of private methods.
For some reason we could not make use of this feature but had to make the respective calls `protected` (cf. respective TODOs in the source of `ClientFactory`).
* PowerMock only works with JUnit 4 so far.
There is a JUnit 5 extension prepared already, cf. https://github.com/powermock/powermock/pull/1146[powermock/pull/1146].
+
[IMPORTANT]
====
For a local build it is currently recommended to check out/clone https://github.com/ascheman/powermock/tree/junit-jupiter-extension[].
Running `./gradlew publishToMavenLocal` will install the proper version (2.0.10).
====
......@@ -217,6 +217,7 @@ public final class ClientFactory {
return new JWECryptoService(messageDigestService);
}
// TODO Make this private again - PowerMock should allow to mock private methods!
protected HttpClient getHttpClient() {
final BuildInfo buildInfo = loadBuildInfo();
if (config.isProxySet()) {
......@@ -237,6 +238,7 @@ public final class ClientFactory {
return new SecurityEventTokenService(config, validationService, rsaKey);
}
// TODO Make this private again - PowerMock should allow to mock private methods!
protected KeyService getKeyService(final HttpClient httpClient, final OAuthService authService, final SubmissionService submissionService, final ValidationService validator) {
return new PublicKeyService(config, httpClient, authService, submissionService, validator);
}
......
......@@ -117,10 +117,12 @@ public class MyStepdefs {
assertThat(createSubmission.getCallback().getUri(), is(URI.create(CALLBACK_URL)));
}
// tag::theSubmissionContainsTheSecret[]
@And("the submission contains the secret")
public void theSubmissionContainsTheSecret() {
assertThat(createSubmission.getCallback().getSecret(), is(CALLBACK_SECRET));
}
// end::theSubmissionContainsTheSecret[]
@And("FIT-Connect is set as reply-channel")
public void fitConnectIsSetAsReplyChannel() {
......@@ -392,13 +394,17 @@ public class MyStepdefs {
any(SubmissionService.class), any(ValidationService.class));
CreatedSubmission createdSubmission = new CreatedSubmission();
when(httpClientMock.post(anyString(), anyMap(), any(CreateSubmission.class), eq(CreatedSubmission.class)))
// tag::mockCreateSubmissionCall[]
when(httpClientMock.post(anyString(), anyMap(),
any(CreateSubmission.class), eq(CreatedSubmission.class))) // <1>
.thenAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
createSubmission = invocation.getArgument(2);
return (new HttpResponse<CreatedSubmission>(200, createdSubmission));
}
});
public Object answer(InvocationOnMock invocation) {
createSubmission
= invocation.getArgument(2); // <2>
return (new HttpResponse<CreatedSubmission>(200, createdSubmission));
}
});
// end::mockCreateSubmissionCall[]
when(httpClientMock.put(anyString(), anyMap(), any(SubmitSubmission.class), eq(Submission.class)))
.thenAnswer(new Answer() {
......
# TODO This file should be renamed to "bidiko-sdks.feature" in order to align it with the original file
Feature: Bi-Directional Communication in SDKs
The (.NET and Java) SDKs should enable usage of the bi-directional communication API.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment