WebTau Runner

You can use webtau http. , graphql. , browser. , cli. , db. methods as in a junit 5 tests, but to enable reporting you need to use @Webtau annotation Groovy package com.example.tests.junit5 import org.testingisdocumenting.webtau.junit5.WebTau import org.junit.jupiter.api.* import static org.testingisdocumenting.webtau.WebTauGroovyDsl.* @WebTau @TestMethodOrder(MethodOrderer.OrderAnnotation) // forcing methods execution order @DisplayName("customer CRUD") class CustomerCrudSeparatedGroovyTest { private static def customerPayload = [firstName: 'FN', lastName : 'LN'] private static def changedCustomerPayload = [*: customerPayload, lastName: 'NLN'] private static def id @BeforeAll @DisplayName("create customer") // optional friendly name for reporting purposes static void createCustomer() { id = http.post("/customers", customerPayload) { body.id // using body prefix is required in this case as id conflicts with class field name } id.shouldNot == 0 } @Test @Order(1) @DisplayName("read customer") void read() { http.get("/customers/$id") { body.should == customerPayload } } @Test @Order(2) // order dependence saves from creating customer on every test @DisplayName("update customer") void update() { http.put("/customers/$id", changedCustomerPayload) { body.should == changedCustomerPayload } http.get("/customers/$id") { body.should == changedCustomerPayload } } @Test @Order(3) // but you can still run each method independently @DisplayName("delete customer") void delete() { http.delete("/customers/$id") { header.statusCode.should == 204 } http.get("/customers/$id") { header.statusCode.should == 404 } id = -1 // marking as deleted to let cleanup step know that no delete is required } @AfterAll static void cleanup() { // optional (since we create new ids all the time) step to keep your environment clean if (id == -1) { return } http.delete("/customers/$id") } } Java package com.example.tests.junit5; import org.testingisdocumenting.webtau.junit5.WebTau; import org.junit.jupiter.api.*; import java.util.Map; import static org.testingisdocumenting.webtau.WebTauDsl.*; @WebTau @TestMethodOrder(MethodOrderer.OrderAnnotation.class) // forcing methods execution order @DisplayName("customer CRUD") public class CustomerCrudSeparatedJavaTest { private static final Map<String, ?> customerPayload = aMapOf( "firstName", "FN", "lastName", "LN" ); private static final Map<String, ?> changedCustomerPayload = aMapOf( customerPayload, "lastName", "NLN"); private static int id; @BeforeAll @DisplayName("create customer") // optional friendly name for reporting purposes static void createCustomer() { id = http.post("/customers", customerPayload, ((header, body) -> { return body.get("id"); })); actual(id).shouldNot(equal(0)); } @Test @Order(1) @DisplayName("read customer") void read() { http.get("/customers/" + id, ((header, body) -> { body.should(equal(customerPayload)); })); } @Test @Order(2) // order dependence saves from creating customer on every test @DisplayName("update customer") void update() { http.put("/customers/" + id, changedCustomerPayload, ((header, body) -> { body.should(equal(changedCustomerPayload)); })); http.get("/customers/" + id, ((header, body) -> { body.should(equal(changedCustomerPayload)); })); } @Test @Order(3) // but you can still run each method independently @DisplayName("delete customer") void delete() { http.delete("/customers/" + id, ((header, body) -> { header.statusCode().should(equal(204)); })); http.get("/customers/" + id, ((header, body) -> { header.statusCode().should(equal(404)); })); id = -1; // marking as deleted to let cleanup step know that no delete is required } @AfterAll static void cleanup() { // optional (since we create new ids all the time) step to keep your environment clean if (id == -1) { return; } http.delete("/customers/" + id); } }

BeforeAll/AfterAll

Use @BeforeAll and @AfterAll to prepare and cleanup resources required for multiple test methods. Groovy package com.example.tests.junit5 import org.testingisdocumenting.webtau.junit5.WebTau import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import static org.testingisdocumenting.webtau.WebTauDsl.http @WebTau @DisplayName("customer query") class CustomerQueryGroovyTest { private static def id1 // keep track of created ids to assert and cleanup later private static def id2 private static def id3 @BeforeAll @DisplayName("create customers") static void createCustomers() { id1 = createCustomer("CQ_FN1", "CQ_LN1") id2 = createCustomer("CQ_FN1", "CQ_LN2") id3 = createCustomer("CQ_FN2", "CQ_LN2") } @Test @DisplayName("query by first name") void queryByFirstName() { http.get("/customers/search/first-name", [name: "CQ_FN1"]) { body.should == ["*id" | "firstName" | "lastName"] { // star(*) marks key column so assertion is order agnostic __________________________________ id1 | "CQ_FN1" | "CQ_LN1" id2 | "CQ_FN1" | "CQ_LN2" } } } @Test @DisplayName("query by last name") void queryByLastName() { http.get("/customers/search/last-name", [name: "CQ_LN2"]) { body.should == ["*id" | "firstName" | "lastName"] { __________________________________ id2 | "CQ_FN1" | "CQ_LN2" id3 | "CQ_FN2" | "CQ_LN2" } } } @AfterAll @DisplayName("clean up") static void cleanup() { [id1, id2, id3].each { http.delete("/customers/$it") } } private static def createCustomer(String firstName, String lastName) { return http.post("/customers", [firstName: firstName, lastName: lastName]) { id } } } Java package com.example.tests.junit5; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.Map; import java.util.stream.Stream; import static org.testingisdocumenting.webtau.WebTauDsl.*; @DisplayName("customer query") class CustomerQueryJavaTest { private static Integer id1; // keep track of created ids to assert and cleanup later private static Integer id2; private static Integer id3; @BeforeAll @DisplayName("create customers") static void createCustomers() { id1 = createCustomer("CQ_FN1", "CQ_LN1"); id2 = createCustomer("CQ_FN1", "CQ_LN2"); id3 = createCustomer("CQ_FN2", "CQ_LN2"); } @Test @DisplayName("query by first name") void queryByFirstName() { http.get("/customers/search/first-name", http.query("name", "CQ_FN1"), (header, body) -> { body.should(equal(table("*id", "firstName", "lastName", // star(*) marks key column so assertion is order agnostic ________________________________, id1, "CQ_FN1" , "CQ_LN1", id2, "CQ_FN1" , "CQ_LN2"))); }); } @Test @DisplayName("query by last name") void queryByLastName() { http.get("/customers/search/last-name", http.query("name", "CQ_LN2"), (header, body) -> { body.should(equal(table("*id", "firstName", "lastName", ________________________________, id2, "CQ_FN1" , "CQ_LN2", id3, "CQ_FN2" , "CQ_LN2"))); }); } @AfterAll @DisplayName("clean up") static void cleanup() { Stream.of(id1, id2, id3).forEach(id -> http.delete("/customers/" + id)); } private static int createCustomer(String firstName, String lastName) { Map<String, Object> payload = aMapOf( "firstName", firstName, "lastName", lastName); return http.post("/customers", payload, ((header, body) -> { return body.get("id"); })); } }

Maven Import

<dependency> <groupId>org.testingisdocumenting.webtau</groupId> <artifactId>webtau-junit5</artifactId> <version>1.35</version> </dependency>

TestFactory

With the additional annotation @TestFactory you can use TableData as an easy-to-read source of similar but independent tests where each row is treated as its own test (comparable to JUnit 4's parameterized tests), optionally with a descriptive label. Here are examples of parameterized tests with and without labels, and how an IDE uses the label for display purposes: @TestFactory def "individual tests use generated display labels"() { ["price" | "quantity" | "outcome"] { _________________________________ 10 | 30 | 300 -10 | 30 | -300 }.test { PriceCalculator.calculate(price, quantity).should == outcome } } @TestFactory def "individual tests can use an optional display label to clarify the use case"() { ["label" | "price" | "quantity" | "outcome"] { ___________________________________________________ "positive price" | 10 | 30 | 300 "negative price" | -10 | 30 | -300 }.test { PriceCalculator.calculate(price, quantity).should == outcome } }