Following from this issue question where I was very much off track, I now have a working example of testing a Uni of type POJO / Bean.
This is very much a "getting started" example.
I am posting all this for newcomers like me. There were some "gotchas" along the way. ;-)
Any refinements and suggestions would be gratefully received.
(As an aside: To help others who are as new as I am, and who might stumble on this post, I have included the folder structure screenshot (VSCode) because it took me a while to work out where the files should go. It is obvious now, but wasn't when I was starting out. ;-) )
The test_my_greeting.feature
file:
Feature: Test My Greeting Service
Scenario: Test Greeting
Given my name is "<name>"
When I send a greeting
Then the reply should be "<reply>"
Examples:
| name | reply |
| fred | Hello fred |
| sally | Hello sally |
| kim | Sorry, kim, I don't know you. |
# This one should fail
| kim | Who dat? |
The TestRunner.java
file:
package org.acme;
import io.quarkiverse.cucumber.CucumberOptions;
import io.quarkiverse.cucumber.CucumberQuarkusTest;
import io.quarkus.test.junit.main.QuarkusMainTest;
@QuarkusMainTest
// The standard Cucumber for JVM options.
@CucumberOptions(
// The root path to say where the feature files are located.
features = { "src/test/resources" },
// The formatter.
// html:target refers to the /target folder in your java project
// so the report will be /target/cucumber-reports/report.html
plugin = { "pretty", "html:target/cucumber-reports/report.html" }
)
public class TestRunner extends CucumberQuarkusTest {
public static void main(String[] args) {
runMain(TestRunner.class, args);
}
}
Again, for newbies: you can view the report.html
in your browser by using the Finder / File Manager at eg: file:///Path/To/Your/Project/target/cucumber-reports/report.html
The GreetingService.java
file:
package org.acme;
import javax.enterprise.context.ApplicationScoped;
import io.smallrye.mutiny.Uni;
@ApplicationScoped
public class GreetingService {
public String greeting (String name) {
if ("fred,sally".contains(name)) {
return "Hello " + name;
} else {
return "Sorry, " + name + ", I don't know you.";
}
}
public Uni<String> greetingUni (String name) {
return Uni.createFrom().item(greeting(name));
}
public Uni<Packet> greetingPacketUni (String name) {
Packet packet = new Packet();
packet.setSuccess(true);
packet.setAction("greet");
// Calculate the greeting message and set it
packet.setMessage(greeting(name));
return Uni.createFrom().item(packet);
}
}
The Packet.java POJO / Bean file:
package org.acme;
public class Packet {
Boolean success = false;
String message = "";
String data = "";
String action = "";
// Getters and Setters here ...
@Override
public String toString() {
return "action=" + this.getAction() + ", success=" + this.getSuccess() + ", data=" + this.getData() + ", message=" + this.getMessage();
}
@Override
public boolean equals(Object o) {
// Note for newbies like me:
// Without this override Java will always return false for equals()
// when comparing the expected Packet and the one we get from the Uni,
// even when the properties are identical.
// You need to tell the JVM what "equals" actually means in this context.
// The reason is:
// Mutiny .assertItem() in your THEN clause eventually calls item.equals(expected) in
// io.smallrye.mutiny.helpers.test.AssertionHelper.shouldHaveReceived()
// and this override is what is used to determine equality.
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Packet o1 = (Packet) o;
// Note: The properties you include for equality testing are up to you.
// The other props could differ but you are only testing the "message" prop here.
// Do the String comparison on the message.
Boolean eq = message.equals(o1.message);
// Just FYI
System.out.println("Packet CHECK EQ=" + eq + " for <" + message + "> <" + o1.message + ">");
return eq;
}
}
And finally, the TestGreeting.java
file:
package org.acme;
import static org.junit.jupiter.api.Assertions.assertEquals;
import javax.inject.Inject;
import io.cucumber.java.en.*;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.helpers.test.UniAssertSubscriber;
public class TestGreeting {
@Inject
GreetingService greetingService;
String testName = "";
UniAssertSubscriber<Packet> assertSubscriberPacketUni = null;
Uni<Packet> greetingPacketUni = null;
@Given("my name is {string}")
public void my_name_is(String name) {
testName = name;
System.out.println("GIVEN with " + testName);
}
@When("I send a greeting")
public void i_send_a_greeting() {
System.out.println("WHEN with " + testName);
// Create the Uni to be tested
Uni<Packet> greetingPacketUni = greetingService.greetingPacketUni(testName);
// Subscribe with the UniAssertSubscriber, which runs the Uni under test
assertSubscriberPacketUni = greetingPacketUni.subscribe().withSubscriber(UniAssertSubscriber.create());
}
@Then("the reply should be {string}")
public void the_reply_should_be(String reply) {
System.out.println("THEN for " + testName + " expected reply to be: " + reply);
// Build an expected Packet. For this example we will assume only the "reply" prop changes.
Packet expectedPacket = new Packet();
expectedPacket.setSuccess(true);
expectedPacket.setAction("greet");
expectedPacket.setMessage(reply); // <== from the feature file example
// Do the assertion.
// Compare the Packet item returned from the Uni under test to the expected one we just created above.
// Note: See the equals() override and comment in Packet.java
// Note: Mutiny 1.7.0 docs are not correct: as per: https://github.com/smallrye/smallrye-mutiny/discussions/1057
// We need to use awaitItem() instead of assertCompleted()
// ie not: assertSubscriberPacketUni.assertCompleted().assertItem(expectedPacket);
// but instead:
assertSubscriberPacketUni.awaitItem().assertItem(expectedPacket);
}
}
And this is what it looks like in the report.html file:
Again, any suggestions are welcome.
Cheers,
Murray