Mutation testing

Quality assurance consists of many activities, but often our primary goal is to verify and validate software our team is working on. Through various means we try to establish whether it fulfills customer requirements and other criteria like code quality, documentation, security, and so on. But who ensures that our job was done correctly? Even when we have a colleague who can review our test artifacts, they probably don't have enough time or context to do this thoroughly due to their own commitments.

Collection of various comic books. An X-Men comic book - a series about mutants - is in the focus.

Photo by Erik Mclean

This is why I always liked mutation testing as it aims to evaluate the quality of tests themselves. The idea is that we introduce small changes in the software under test and see if our tests managed to catch unexpected behavior by failing. We can, for instance, modify an SQL query from this:

select * from shopping_carts where user_id = ?

To this:

select * from shopping_carts where user_id != ?

So even though an insert method somewhere else would correctly add items to a shopping cart, it'll appear to be empty to the user due to the inverted where clause. If we don't have a test to validate the shopping cart after adding some items, all our tests will pass, revealing a gap in coverage. Like any other form of testing, mutation testing isn't exhaustive and it cannot prove the absence of bugs. Still, it can be a useful tool.

Read more...

How to handle decimal numbers in form params with WireMock

I wrote about WireMock last year. It's a very powerful tool that lets us replace external services with stubs while testing. Since it can be configured on the fly by tests, it allows us to validate virtually any behavior. Here's how I use it:

How WireMock works: sequence diagram

In this sequence diagram, a test tells WireMock to return a JSON body for every request to /fetch-details. During execution, the test calls /some-endpoint to validate it. This endpoint needs data from an external service to produce a response, so it requests /fetch-details and gets the JSON body that the test fed WireMock at the start. By changing what /fetch-details returns, we can easily simulate various scenarios.

In this post, I want to cover a case I dealt with recently: form data with decimal numbers.

Read more...

How to override Awaitility error messages

Awaitility is an excellent Java library. It's especially useful for working with eventually consistent APIs. For instance, when a client sends a POST, PUT, or DELETE request, you might need to make several GET requests before observing the changes, which is terrible for automated tests. A common solution to this problem is to use Awatility. You can ask it to execute code for n seconds until the request succeeds or the timer expires.

await()
    .atMost(Duration.ofSeconds(5))
    .until(() -> apiClient().get(id).getStatus().equals("expected"));

Unfortunately, the default error messages could use some work.

Hourglass on Brown Wooden Frame

Photo by Mike

Read more...

Lombok compilation error: cannot find symbol

I was working on the service that powers this blog when I stumbled upon an odd issue.

[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] /app/service/src/main/java/ee/fakeplastictrees/blog/service/user/model/UserExceptionFactory.java:[8,1] cannot find symbol
  symbol:   static withData
  location: class ee.fakeplastictrees.blog.service.core.exceiption.PublicExceptionFactory

There were 52 more errors like this in different classes. I hadn't changed the Java version or any dependencies since the previous build. Moreover, most of the files that reported failures had absolutely no changes in them. I was able to build this code not long ago! It was as if lombok had suddenly stopped working for no obvious reason. Nothing in maven's log pointed to the root cause.

A confused man screaming at his laptop

Photo: not exactly me, but the same energy.

Search results were misleading and suggested that my project was configured incorrectly, even though it had worked in the exact same environment just a couple of weeks ago. Eventually, I found the answer!

Read more...

How test engineers can use WireMock to substitute external services

It might be hard to find a fully independent service today. Even in the case of a monolithic architecture, where all components of the software are tightly coupled within a single codebase, there's still a need to integrate with the outer world. For instance, internal banking logic can be self-sufficient. But functionalities like police security checks and interbank money transfers can't be done in isolation.

During the development phase of a product or feature, programmers may write stubs and mocks to substitute missing dependencies. As QA engineers, however, we want to test the code that will go into production, touching the same statements and execution paths as the real data. Furthermore, we need to validate the system's behavior with various inputs, hence it should be possible to change how the external service responds to the program under test.

WireMock is a tool that enables us to do exactly this. It launches an HTTP server that can be configured to respond to specific requests in a desired manner. Let's say there is a system that should verify the recipient's IBAN against a database. If the response is positive, the transfer proceeds; if negative, the transfer must be blocked.

Read more...

Secret Handshake: Solving an exercise using bitwise operators and comparing performance

A couple of days ago, I took on an interesting task on Exercism. You can find the full description here. The goal is to create a function that takes a number between 1 and 31 and converts it to a list of actions. The sequence is defined by the five rightmost digits of the number when converted to binary. If you're unfamiliar with the binary system, I recommend reading this explanation first.

In this post, I'll discuss how I gradually improved my initial solution and share some interesting findings about the performance of different approaches.

Read more...

Testcontainers and Spring: Datasource becomes inaccessible between test classes

Testcontainers is a great tool for managing external services required for the proper functioning of an entire application or its components during testing. It automatically deploys these services before a test begins and then stops them once testing is over. This simplifies the process of running tests across multiple environments.

While working on integration tests for the service behind this blog, I stumbled upon an issue. It lies on the border between Testcontainers and Spring. When executing a single test or an entire class, everything worked as expected. But when executing multiple test classes (e.g. with ./gradlew test), the database became inaccessible.

In this post I'm exploring one of the possible solutions to this issue.

Read more...