Testing OpenAPI specification compliance in Quarkus with Prism

Quarkus is a good framework choice when developing RESTful APIs in an API-first manner by using the OpenAPI generator to generate models and API interfaces. That way, larger parts of compliance with the API specification are already handled by the compiler. However, not every inconsistency can be detected this way. In this post, I demonstrate how to integrate Stoplight’s Prism proxy into the test infrastructure of a Quarkus Kotlin project for validating OpenAPI specification compliance automatically as part of the API tests. Table of contentsMotivationValidating OpenAPI compliance with PrismIntegrating Prism into QuarkusImplementing a Quarkus test resourceRedirecting test requests through the test resourceDetecting and fixing a bug in the example projectUsing the test resource in integration testsTrading confidence for test execution timeSummary MotivationIn API-first development with Quarkus and Kotlin I have shown a basic setup for Quarkus to support API-first development. As a short recap, API-first development is an approach of developing (RESTful) APIs, where a formal specification of the intended API (changes) is created before implementing the API provider or consumers. That way, we can make use of the specification for code generation, parallel development, and for verification purposes. A common issue seen when using APIs based on their documentation is that the actual API implementation differs from what is documented. The setup shown before prevents larger parts of this problem by leveraging code generation through the OpenAPI Generator. Moreover, the compiler catches a few error cases when generated interfaces are not implemented properly. However, not every aspect of API compliance is validated this way and we would still be able to provide an implementation that deviates from the specification. Here’s a short example demonstrating one of the more obvious ways we can still deviate: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # ... paths: /pets: get: summary: List all pets operationId: listPets responses: '200': description: An array of pets content: application/json: schema: $ref: "#/components/schemas/Pets" # ... components: schemas: PetId: type: integer format: int64 Pet: type: object required: - id - name properties: id: $ref: "#/components/schemas/PetId" name: type: string Pets: type: array items: $ref: "#/components/schemas/Pet" ...

December 4, 2024 · updated December 6, 2024 · 16 min

API-first development with Quarkus and Kotlin

API-first development is a way of developing distributed systems that makes the API specifications of the system components a first-class citizen in the development process. This approach promises to better control the challenges of loosely coupled components communicating via inter-process communication so that the benefits of separating a monolith into individual components pay off sooner. In this post, I will give a brief introduction on what API-first development is and how my preferred JVM-based setup for developing microservices using the API-first principles looks like at the moment. Table of contentsWhat is API-first developmentAPI-first development for REST APIs with OpenAPIAPI-first and code-first development with OpenAPISetting up an API-first development workflow in QuarkusAdd the API definition to the projectEnable SwaggerUIOnly serve the defined API in SwaggerUILet Quarkus determine the server in the OpenAPI specificationGenerate API stubs using the OpenAPI GeneratorImplementing a generated API resourceSummaryBibliography What is API-first developmentIn a distrusted system, such as a microservice architecture, inter-process communication plays a crucial role. But it comes at a price. Distributed systems are often harder to debug and to refactor. In a monolith, the IDE and the compiler help refactoring programming APIs such as an interface or abstract base class. A mismatch between the API provider and the consumer will usually be caught by the compiler. In a distributed system, usually no compiler helps when changing APIs and we cannot rely on it to find mismatches between consumers and providers. Without care and special testing techniques, errors will only surface at runtime, making the system more brittle and changes more costly. Therefore, it is important that: APIs are well-designed so that breaking or incompatible changes are infrequentif required, breaking or incompatible changes happen in a controlled way that prevents API consumers from suddenly failing without prior notice....

August 16, 2024 · updated December 6, 2024 · 16 min

On writing useful (unit) tests

Throughout the years I have seen a lot of people struggling with writing useful and readable test cases. And I have also seen a lot of existing test code that has more resemblance with a bowl of spaghetti than it helps ensuring software quality. While some say that writing tests at all is better than not having automated tests, well-structured test code is vital to achieving most of the benefits claimed by the TDD community. As structuring tests seems to be a real problem for many, this post collects some personal advices on how to create a clean test code base that helps beyond technically checking program correctness. Table of contentsThe benefits of well-structured testsTests are a means of communicationTests as a tool for debuggingPreconditions for good testsAbstractionDependency injectionGuidelines for writing test casesVerify one aspect per test caseUse test case names to express readable requirementsTest your business, not someone else’sClarify requirements with syntax and features, don’t dilute themWhat to test and what not to testHow to provide test doubles: stubs and mocksConclusionBibliography The benefits of well-structured testsWhy does the structure of test code actually matter? Why should one bother with achieving clean test cases if a convoluted test function in the end verifies the same aspects of the code? There are (at least) two good reasons that explain why investing work in the structure of test cases is important. Note: This blog post focuses on techniques regarding tests that are written in a typical general-purpose programming language with tools such as xUnit-like frameworks. This usually isn’t the case only for unit tests. Also higher levels in the testing pyramid are often realized this way and the general recommendations given here are also applicable on this level. I do not specifically address tests realized using other, more declarative formalisms such as BDD-style testing. Still, some things probably apply there as well. Tests are a means of communicationAlthough reliably verifying that code performs and continues to perform the intended function is probably the primary reason for writing automated tests, well-structured tests can serve more purposes for developers, most of them boiling down to communication. The disputable Uncle Bob Martin has coined a famous quote in this regard: Indeed, the ratio of time spent reading versus writing is well over 10:1. We are constantly reading old code as part of the effort to write new code. …​ so making it easy to read makes it easier to write. — [Martin2009] p. 14...

August 12, 2021 · updated September 12, 2021 · 26 min