{"id":20877438,"url":"https://github.com/t1/wunderbar","last_synced_at":"2025-05-12T16:30:36.934Z","repository":{"id":37798242,"uuid":"338375834","full_name":"t1/wunderbar","owner":"t1","description":"Code-first, low ceremony Consumer Driven Contracts test tool for Java natives","archived":false,"fork":false,"pushed_at":"2025-05-07T16:56:42.000Z","size":1172,"stargazers_count":7,"open_issues_count":11,"forks_count":0,"subscribers_count":2,"default_branch":"trunk","last_synced_at":"2025-05-07T17:47:38.126Z","etag":null,"topics":["api-first","code-first","consumer-driven-contracts","graphql","jakarta-ee","java","microprofile","microprofile-graphql","microprofile-restclient","quarkus"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/t1.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-02-12T16:44:19.000Z","updated_at":"2025-05-07T16:56:44.000Z","dependencies_parsed_at":"2023-10-16T23:01:04.469Z","dependency_job_id":"86f666c7-bfe6-4ec3-b522-a5dcad760620","html_url":"https://github.com/t1/wunderbar","commit_stats":null,"previous_names":[],"tags_count":46,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t1%2Fwunderbar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t1%2Fwunderbar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t1%2Fwunderbar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t1%2Fwunderbar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/t1","download_url":"https://codeload.github.com/t1/wunderbar/tar.gz/refs/heads/trunk","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253776657,"owners_count":21962521,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-first","code-first","consumer-driven-contracts","graphql","jakarta-ee","java","microprofile","microprofile-graphql","microprofile-restclient","quarkus"],"created_at":"2024-11-18T06:57:01.713Z","updated_at":"2025-05-12T16:30:35.986Z","avatar_url":"https://github.com/t1.png","language":"Java","readme":"= BAR :: Behaviour ARchive image:https://maven-badges.herokuapp.com/maven-central/com.github.t1/wunderbar.junit/badge.svg[link=https://search.maven.org/artifact/com.github.t1/wunderbar.junit] image:https://github.com/t1/wunderbar/actions/workflows/maven.yml/badge.svg[link=https://github.com/t1/wunderbar/actions/workflows/maven.yml]\n:toc: macro\n\n[.right]\ntoc::[]\n\nCode-first, low ceremony https://martinfowler.com/articles/consumerDrivenContracts.html[Consumer Driven Contracts] test tool for Java natives.\n\nLet's pick that apart:\n\n\"code-first\": there's a lot of debate about designing APIs schema-first (a.k.a.\nAPI-first) vs. code-first.\nI absolutely agree that (not only for public APIs) it's very important to design APIs in a consistent and easily approachable way.\nI even think that it's actually more important to capture the real use-cases of real consumers of an API.\nIt's just way too easy to design something that looks good as a schema but is clumsy to use in practice.\nAnd by lowering the bar to specify the (changes to an) API as much as possible, consumers are invited to help evolve the API in a pragmatic way that is easy to use.\n\n\"low ceremony\": use it just like you'd use https://site.mockito.org[Mockito] to test your client code.\nEasily scale that from Unit to Integration Testfootnote:[The terms \"Integration Test\", \"System Test\", and \"Acceptance Test\" are used in other contexts with slightly different meaning. This is definitively confusing, but introducing new terms or even numbers would make it even harder to understand. So this is the lesser of two evils.], i.e. integrate your client code with an actual http server mocking the responses the tests expect, in order to also cover all the (de)serialization involved.\nOr even go for System Tests, where your service runs in a production-like environment and calls a pre-deployed mock service.\nEither way, this helps clients to test their own code and...\n\n\"Consumer Driven Contracts\": ... as a _side effect_, WunderBar also records the expected http interactions into a Behavior ARchive `bar`, a simple zip file containing properties files for the method, uri, and headers, and json files for the bodiesfootnote:[We currently don't see the necessity to support other content types, open an issue if you _do_ need `xml` or whatever.].\nHand this file over to the API providers, so they can use it to verify compliance with the clients' requirements.\nSee the article linked above.\nJust to make it clear: just because the consumer drives the API contract doesn't mean that the provider is expected to comply blindly.\nIt's only a starting point for the \"contract negotiation\".\nThe provider has to evolve a domain model that is consistent over all consumers: the BAR files are not a replacement for talking; they just bring it to the precision that the machines foolishly insist on; and a long-term test suite of acceptance tests.\nStarting from the API Consumer perspective is actually just a very natural way of defining an API: from the real requirements.\nBut you must generally take care that no client application logic seeps into your backend domain, i.e. views, flows, etc.\n\n\"Java native\": The consumer/client can use REST via https://github.com/eclipse/microprofile-rest-client[MicroProfile REST Client] or https://graphql.org[GraphQL] via https://github.com/smallrye/smallrye-graphql/tree/main/client/api[SmallRye GraphQL Client], while the server can use any technology stack that can run JUnit 5 tests (or you can use the `bar` files directly).\nFor the details see below.\n\n== 2 Minute API Consumer Intro\n\nSay you're developing an Order service that uses data from a Product service, i.e. it _consumes_ an API.\nYou probably have a `ProductsGateway` or `ProductsResolver` class that uses a `ProductsClient` interface with annotations from https://github.com/eclipse/microprofile-rest-client[MP Rest Client] or https://github.com/smallrye/smallrye-graphql/tree/main/client/api[SmallRye GraphQL Client] that you unit-test with Mockito:\n\n[source,java]\n----\n@RegisterRestClient @Path(\"/products\")\npublic interface ProductsClient {\n    @GET @Path(\"/{id}\")\n    Product product(@PathParam(\"id\") String id);\n}\n\n// or alternatively\n\n@GraphQLClientApi\npublic interface ProductsClient {\n    Product product(String id);\n}\n\n// test\n\n@ExtendWith(MockitoExtension.class)\nclass ProductsGatewayMockitoTest {\n    @Mock ProductsClient products;\n    @InjectMocks ProductsGateway gateway;\n\n    @Test void shouldGetProduct() {\n        given(products.product(PRODUCT_ID)).willReturn(PRODUCT);\n\n        var response = gateway.product(ORDER_ITEM);\n\n        then(response).usingRecursiveComparison().isEqualTo(PRODUCT);\n    }\n}\n----\n\nInstead of using the Mockito extension with its annotations and the static `given` method import, you can simply use those from WunderBar.\nThey are more limited than Mockito, but have the same style, same behavior, just different logs:\n\n[source,java]\n----\n@WunderBarApiConsumer\nclass ProductsGatewayTest {\n    @Service ProductsClient products;\n    @SystemUnderTest ProductsGateway gateway;\n\n    @Test void shouldGetProduct() {\n        given(products.product(PRODUCT_ID)).returns(PRODUCT);\n\n        var response = gateway.product(ITEM);\n\n        then(response).usingRecursiveComparison().isEqualTo(PRODUCT);\n    }\n}\n----\n\nNote that you can also use the Mockito syntax `given(...).willReturn(...)`, but using `returns` makes sure you don't accidentally use the `given` from Mockito.\n\nTo make things interesting, you can change the `@WunderBarApiConsumer` annotation to `@WunderBarApiConsumer(level = INTEGRATION)` (or simply change the test name to end with `IT`, short for Integration Test): WunderBar now starts a mock server exposing the behavior you just stubbed, i.e. it will reply to your real http request with the proper product http response.\nNo code changes needed, and now it fully tests your REST or GraphQL client annotations and (de)serialization of your POJOs.\n\nThis is nice, but as a welcome side effect, it _records_ the requests and responses you need for your code to work, and saves it in a `wunder.bar` file (Behavior ARchive).\nGive this file to your API provider, so they can check if their service complies to your requirements.\nYou can even deploy this file, together with your other maven artefacts, e.g. by using the `attach-artifact` goal of the `build-helper-maven-plugin`; for an example, look at the pom in the https://github.com/t1/wunderbar/blob/trunk/demo/order/pom.xml[`demo/order`] submodule.\nNote that you'll get a `Failed to install artifact` error when running `mvn clean install -DskipTests`, because you skip the tests that generate the `bar` files, so the plugin can't attach them.\nMost of the time, executing `clean` is not necessary (and slows your build down); and running `install` before releasing your software is only useful for libraries, not for applications; simply run `mvn package -DskipTests` instead.\nIf you still need the `install`, you can also pass `-Dbuildhelper.skipAttach`.\n\nYou can disable recording for one stub by calling `withoutRecording`, e.g. when testing the error handling in your consumer code, so this interaction is not an expected behavior of the API provider.\n\n=== Generating Test Data (Consumer)\n\nGenerating good test data can become tedious; numbers should be rather small, positive, and (above all) unique, so the value are easy to handle and recognize.\nWunderBar provides an extensible mechanism to generate test data that fulfills these requirements, using a simple counter starting for every test.\nAnd it logs the values it generated for what purpose, so you can easily find the source for a value.\nYou can generate a value for a field or parameter by annotating it as https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/consumer/Some.java[`@Some`].\n\n[source,java]\n----\n@WunderBarApiConsumer\nclass ProductResolverTest {\n    @Test void shouldUpdateProduct(@Some int newPrice) {\n        given(/*...*/).returns(product.withPrice(newPrice));\n\n        var resolvedProduct = resolver.productWithPriceUpdate(item, newPrice);\n\n        then(resolvedProduct).usingRecursiveComparison()\n            .isEqualTo(product.withPrice(newPrice));\n    }\n}\n----\n\n`@Some int newPrice` could also be a field, and it would work exactly the same.\n\nNote how the `newPrice` parameter is used throughout the test.\nThis makes it easier for the reader of your code to understand which test value is which.\nSometimes, you'll want to provide some constant value; as they should be distinct from the generated values, use values below 100, which is where `@Some` will start counting from by default.\nAnd the generator will fail, if you generate values beyond `Short.MAX_VALUE` = `32767` = `2^15^-1` = `0x7FFF`; generating a `byte` fails sooner, obviously.\n\nOut-of-the-box, you can generate the primitive types `byte`, `char`, `short`, `int`, `long`, `float`, `double` (or their wrapper types `Integer`, etc.), and some basic types like `String`, `URI`, `LocalDateTime`, etc. (see https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/consumer/SomeBasics[here] for the full list), but obviously not `boolean`: they can hardly be considered unique.\nYou can change the starting point by calling `SomeBasics#reset`, e.g. in a `@BeforeEach`.\nYou can also generate `List` or `Set`, which will contain exactly one generated element, and instances of arbitrary classes, which will recursively generate values for every field.\n\nTo generate your own data, e.g., `@Some Product product`, you can register your own generator class: `@Register(SomeProducts.class)`, where `SomeProducts` implements https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/consumer/SomeData.java[`SomeData`].\nThe `@Some` annotation takes an optional list of String tags that are passed to custom generators, along with the `AnnotatedElement` location, so it can fine-control what data it should generate, e.g. to generate `invalid` objects.\n\nYou can also inject an instance of `SomeGenerator` into your generator's constructor to dynamically generate other values you depend on, or to look up the location or tags of an actual value.\nFor a full example see https://github.com/t1/wunderbar/blob/trunk/junit/src/test/java/test/consumer/SomeProduct.java[here].\n\n== 2 Minute API Provider Intro\n\nWhen you implement an API (i.e. you provide it), you can load a suite of tests that has been stored in a `wunder.bar` file, and run them against your service:\n\n[source,java]\n----\n@WunderBarApiProvider(baseUri = \"http://localhost:8080\")\nclass ConsumerDrivenAT {\n    @TestFactory DynamicNode orderTests() {\n        return findTestsIn(\"wunder.bar\");\n    }\n}\n----\n\nThere are several ways to load `bar` files; e.g., you can also load them from maven coordinates.\nSee the public methods in the https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/provider/WunderBarTestFinder.java[`WunderBarTestFinder`] class for details.\n\nThe requirements will be more specific than your service, but that's a good thing: thankfully, your service will be lenient in some cases; e.g. it accepts different content type encodings, like `ISO-8859-1` or `utf-8`.\nIn this way, a client can change some details of its technical requirements, e.g. by requesting a different encoding or even content type (e.g. `json` instead of `xml`); as long as your service supports it, the tests continue to pass.\nAnd if it doesn't support it, it will show up as soon as the new version of the bar file runs.\n\nIf the test data in your service is static and matches the expectations of your clients/consumers, that's it!\nBut to be honest, managing test data is generally a nastily complex issue, and WunderBar can help, but can't make it go away completely.\n\n=== Managing Test Data (Provider)\n\nConsumer Driven Contract testing is about the _structure_ of the data, the API.\nBut the requests and responses in a `bar` file also contain some more or less random _data_ itself.\nThe most common reflex is to create exactly that data in your test system, which is okay as long as the data is very static.\nBut test data often changes or is even deleted for various reasons: some data simply times out, other data is changed by manual as well as automated tests, etc.\nThis demands coordination between different teams, resulting in high effort and brittle tests: they sporadically break without exposing a real bug anywhere but in this communication between people.\n\nYour tests will be much more maintainable, if set up (and maybe clean up) data in your service to match the consumers' requirements, i.e. mostly putting the expected response into your system.\nYou can do so by using some mutating APIs of your service, or by storing and deleting the data directly into your database, or by defining an extra test backdoor API for your service:\neither way, you'll need do this kind of test setup before every test in the BAR (and maybe some cleanup thereafter).\nTo do so, just define a method, annotated as https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/provider/BeforeInteraction.java[`@BeforeInteraction`]footnote:[JUnit invokes the standard JUnit `@Before/AfterEach` methods only once for every test method, not for every test in a `DynamicNode`. WunderBar also calls methods annotated as https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/provider/BeforeDynamicTest.java[`@BeforeDynamicTest`] / https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/provider/AfterDynamicTest.java[`@AfterDynamicTest`]; the difference is that, in some cases, there can be several subsequent interactions within one dynamic test, so methods with `Before/AfterDynamicTest` work on Lists.], taking a single parameter of type https://github.com/t1/wunderbar/blob/trunk/lib/src/main/java/com/github/t1/wunderbar/junit/http/HttpRequest.java[`HttpRequest`], https://github.com/t1/wunderbar/blob/trunk/lib/src/main/java/com/github/t1/wunderbar/junit/http/HttpResponse.java[`HttpResponse`], or https://github.com/t1/wunderbar/blob/trunk/lib/src/main/java/com/github/t1/wunderbar/junit/http/HttpInteraction.java[`HttpInteraction`] (which basically just bundles a request and response).\n\nIn addition to storing the data in your system, you can also manipulate the request or the expected response by returning an `HttpInteraction`, `HttpRequest`, or `HttpResponse` from your `BeforeInteraction` method to modify the interaction, e.g. to replace the dummy credentials from the bar file (xref:credentials[see below]) with real credentials your service will accept.\nThe https://github.com/t1/wunderbar/blob/trunk/lib/src/main/java/com/github/t1/wunderbar/junit/http/HttpRequest.java[`HttpRequest`] and https://github.com/t1/wunderbar/blob/trunk/lib/src/main/java/com/github/t1/wunderbar/junit/http/HttpResponse.java[`HttpResponse`] classes help here with a bunch of convenient methods.\n\nThis works nicely when reading data, but you'll need more for mutating operations; e.g. when a test creates a record in a database, it most often will also generate something like a primary key, which will not match the key in the expected response.footnote:[You actually could create the data in a setup method, manipulate the expected response accordingly, and rely on your service being idempotent, so the real call will return the same data, but this is not only more work but also contra-intuitive. There's a better way.]\nTo manipulate the expected response to match a value from the actual response, write a method annotated as https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/provider/AfterInteraction.java[`@AfterInteraction`].\nYou can't return a request here anymore (as it's already done), but get the actual response with a https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/provider/Actual.java[`@Actual`] annotated `HttpResponse` parameter and use that to manipulate the expected result as needed.\n\nYou can also filter the tests to actually run, by annotating a method as https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/provider/BeforeDynamicTest.java[`@BeforeDynamicTest`], and returning the `List\u003cHttpInteraction\u003e` with tests removed as you wish.\nYou could even add your own, e.g. by duplicating (and then probably modifying) an existing one.\n\nWriting your acceptance tests in this way makes your testing more robust, as you don't have to agree with the consumers of your APIs on any volatile and intransparent assumptions about the test data, e.g. what ids or data fields result in what behavior.\nFor a fully running example, see the demo https://github.com/t1/wunderbar/blob/main/demo/product/src/test/java/test/acceptance/ConsumerDrivenAT.java[ConsumerDrivenAT].\n\n[#credentials]\n== Credentials\n\n`bar` files never contain the secrets of a real `Authorization` header footnote:[They used to say that the username was a secret, too, but when you use good passwords (i.e. really random and really long), this is not necessary anymore, but it makes life so much easier to see the username.].\nThey could contain random values for integration tests, without adding any benefit; for system-level tests (xref:system-level-tests[see below]) against a real service, the interactions would even contain real credentials.\nSo WunderBar only writes dummy values instead.\n\nFor a GraphQL client, you can use the `@AuthorizationHeader` annotation to read the configuration from an MP Config property; but you don't have to actually provide those for an integration test, as they won't be written anyway; a dummy value will be written instead.\nOTOH, a `@Header(name = \"Authorization\")` works normally (but won't be written either).\n\nOn the API provider side, the acceptance test has to replace this value with real credentials, e.g. by returning a modified `HttpRequest` in a `@BeforeInteraction` method.\n\n== Full Dependency Injection\n\nUsing the `@SystemUnderTest` annotation performs only a very limited form of dependency injection.\nFor more complex dependency requirements, it may be appropriate to use, e.g., https://github.com/weld/weld-junit/blob/master/junit5/README.md[`weld-junit5`] as a fully blown CDI testing environment.\nTo do so, do the following steps:\n\n1. add a `test` scope dependency on `org.jboss.weld:weld-junit5`,\n2. annotate your test class with `@EnableWeld` _after_ (this is important) the `@WunderBarApiConsumer` annotation,\n3. instead of `@SystemUnderTest`, use the CDI `@Inject` annotation, and\n4. build a `WeldInitiator` with your classes, and for the services, add a mock bean with a _delayed_ `create` producer of the WunderBar-mocked service field.\n\nThis sums up like this:\n\n[source,java]\n----\n@WunderBarApiConsumer\n@EnableWeld\nclass ProductsResolverWeldIT {\n    @Service Products products;\n    @Inject ProductsResolver resolver;\n\n    @WeldSetup\n    WeldInitiator weld = WeldInitiator.from(ProductsResolver.class, Products.class)\n        .addBeans(MockBean.builder().types(Products.class).create(ctx -\u003e products).build())\n        .build();\n}\n----\n\nIn this way, WunderBar produces the service proxy, and Weld can inject it into your system under test.\nFor a complete example, take a look at https://github.com/t1/wunderbar/blob/main/demo/order/src/test/java/test/graphql/ProductsResolverWeldIT.java[`ProductsResolverWeldIT`].\n\n[#system-level-tests]\n== `SYSTEM` Level Tests\n\nTo go one step further than integration tests, you can use the test level `SYSTEM`, maybe by renaming your test class suffix from `IT` to `ST`.\nThis means that you actually deploy your service to a full environment, often called 'dev stage'.\nThen your service needs to call a running instance of the target systems' API.\nWunderBar provides the https://search.maven.org/artifact/com.github.t1/wunderbar.mock.server[`wunderbar-mock-server`] `war` artifact that you can deploy so your system under test service can reach it and configure your service to do so; no code changes needed.\nConfigure the `@Service#endpoint` to the address of this mock service.\nIf you call a `given` on the stub that's injected into your test, WunderBar prepares this\n\n// TODO finish documentation\n\nYou can use system-level tests to test a real system, as long as you only test with the data that exists in that service, as calling `given` will try\n\n// TODO finish documentation\n\n== Full Documentation\n\nThe full documentation is in the JavaDoc, mainly in the https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/consumer/WunderBarApiConsumer.java[`@WunderBarApiConsumer`] annotation, the https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/consumer/Level.java[`Level`] enum and the https://github.com/t1/wunderbar/blob/main/junit/src/main/java/com/github/t1/wunderbar/junit/consumer/WunderbarExpectationBuilder.java[`WunderbarExpectationBuilder`] for the API consumer (client) side and in the https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/provider/WunderBarApiProvider.java[`@WunderBarApiProvider`] annotation and the https://github.com/t1/wunderbar/blob/trunk/junit/src/main/java/com/github/t1/wunderbar/junit/provider/WunderBarTestFinder.java[`WunderBarTestFinder`] for the API provider (server) side.\n\nThe `demo` module contains two example projects: `order` consumes an API that the `product` service provides.\nBoth in REST and GraphQL and on all test levels.\n\nIf you have further questions, don't hesitate to ask questions on Stack Overflow tagged with https://stackoverflow.com/questions/tagged/wunderbar[wunderbar].\nContributions are also very welcome, of course: start discussions, open issues, add comments, share it online or offline, and if you like it, give it a star on GitHub, please 😁\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ft1%2Fwunderbar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ft1%2Fwunderbar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ft1%2Fwunderbar/lists"}