Thursday, April 7, 2022

Moved to blog.jlo.fail

I've moved! I've switched blogging platforms (i.e. hashnode) and bought my own domain: https://blog.jlo.fail. Check out my first blog "Why a .fail domain?". It will be the home to all my new articles, so subscribe to my newsletter. I plan on keeping all the articles here on blogpost without migrating them to my new domain. Blogspot was great and it met the need of being a low-barrier way to start blogging. It was just time to buy my own domain and have better support for writing developer-focused articles that use markdown. Anyways, see ya there.

Thursday, October 8, 2020

H2 URL with PostgreSQL mode and jOOQ

Just a quick code snippet to show my future self how I was able to finally get H2 working with a jOOQ query that in production hits a PostgreSQL database. I spent hours trying to get this to work. H2 kept complaining about not being able to find the public schema when our jOOQ query was attempting to execut a query like select * from "public"."customer". It's wasn't the schema it was having trouble with but the quotes. I figured this out by pulling up the H2 console and trying the query with and without quotes and without the quotes it worked. So then I figured out that I needed to somehow tell H2 this was for PostgreSQL. H2 has a MODE option that you can set.


The following is the code snippet that finally worked. I have not optimized it. The url value contains all my attempts to get this thing to work. I think it can definitely be trimmed down but that's out of scope for this blog. I'm just trying to save others some time including my future self.
import org.springframework.boot.jdbc.DataSourceBuilder;

DataSourceBuilder.create()
	.driverClassName("org.h2.Driver")
	.url("jdbc:h2:~/.h2/testdb"
		+ ";TRACE_LEVEL_SYSTEM_OUT=3"
		+ ";INIT=create schema if not exists public AUTHORIZATION sa\\"
		+ ";RUNSCRIPT FROM 'classpath:schema.sql'"
		+ ";DB_CLOSE_ON_EXIT=FALSE"
		+ ";SCHEMA=public"
		+ ";MODE=PostgreSQL"
		+ ";DATABASE_TO_LOWER=TRUE")
	.username("sa")
	.password("")
	.build();

Thursday, February 27, 2020

Parameterized Tests: An Underused Technique

Over the past several years I've come to really value Parameterized Tests. I think they are an underused technique that should be considered more often. They make it easier to cover more of the test space and reduce cognitive load by being succinct.

So what are Parameterized Tests? Well they allow developers to run the same test multiple times over a set of different values. Here is a simple Java example using Java's Stream class with AssertJ:

Stream.of(null, "", " ", "false", "FALSE")
        .forEach(value -> assertThat(Boolean.valueOf(value)).isFalse());

Cucumber Inspiration


I first discovered this technique with Cucumber, which has the ability to run a scenario multiple times over a set of different values using a Scenario Outline. Prior to this I was in the habit of copying and pasting a Scenario and tweaking the Given and Then steps; or I wouldn't test all the sad path combinations in an effort to reduce test maintenance. Learning about the Scenario Outline changed my world. I was able to combine multiple scenarios into a single scenario and the result was more comprehendible and easier to maintain. For example, this is a common Scenario Outline we might use to test an API endpoint to ensure it validates the input. Previously, this would have been spread across multiple scenarios or not tested at all.

Scenario Outline: Should return a 400 when signing up with an invalid email address
  When I attempt to sign up with email "<email>"
  Then I should be returned a "400 Bad Request" status code

  Examples:
    | Email                       |
    | ${absent}                   |
    | ${blank}                    |
    | ${whitespace}               |
    | mailto:john.doe@example.com |
    | john.doe@example            |
    | john.doe@example.           |
    | john.doe@example.com.       |
    | john.doe@@example.com       |
    | john.doe@example..com       |
    | john doe@example.com        |

Recognizing Pattern


Once I discovered this concept and got comfortable with it, I explored ways to introduce it in lower level tests like unit and integration tests. But the trick was recognizing when to apply it. Basically, any time you find yourself copying and pasting a test and tweaking the arrange and assert statements you've probably got a good candidate for a Parameterized Test.

For example, let's say you have a class that determines if a number is a prime number or not. Before Parameterized Tests you might have been tempted to write something like the following:

public class PrimeNumberCheckerTestOldSchool {
    @Test
    public void shouldReturnTrueForPrimeNumber() {
        assertThat(PrimeNumberChecker.check(2)).isTrue();
    }

    @Test
    public void shouldReturnFalseForNonPrimeNumber() {
        assertThat(PrimeNumberChecker.check(6)).isFalse();
    }

    @Test
    public void shouldReturnFalseForAnotherNonPrimeNumber() {
        assertThat(PrimeNumberChecker.check(9)).isFalse();
    }

    @Test
    public void shouldReturnTrueForAnotherPrimeNumber() {
        assertThat(PrimeNumberChecker.check(17)).isTrue();
    }
}

Here you can see we are just repeating the test with different inputs and a different result. And since it's spread out across multiple tests it's hard to comprehend and this solution doesn't scale well if we want to add additional tests.

The ideal solution would be to define a sort of truth table, like Cucumber's Example table, that includes a combination of the inputs and expected result in a single test. This is where Parameterized Tests comes in.


JUnit 5


My preferred way to write Parameterized Tests is with JUnit 5 (see Parameterized Tests with JUnit 5). It's a big improvement over the JUnit 4 way of doing Parameterized Tests. We can rewrite the earlier example using the @ParameterizedTest and @CsvSource annotations (see other source annotations):

public class PrimeNumberCheckerTestJUnit5 {
    @ParameterizedTest
    @CsvSource({
            "2,  true",
            "6,  false",
            "9,  false",
            "17, true"
    })
    public void shouldCheckPrimality(final int number, final boolean expected) {
        assertThat(PrimeNumberChecker.check(number)).isEqualTo(expected);
    }
}

Here are some of the benefits of this approach:
  • Easier to comprehend since it's not spread out across 4 test methods.
  • Easier to add/remove additional scenarios.
  • Easier to delete if the Method Under Test (MUT) is removed; all we will have to do is delete one test method!

To write Parameterized Tests with JUnit 5 you need to include the following dependencies. Note the JUnit 5 documentation states this is an "experimental" feature.

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.5.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>5.5.2</version>
    <scope>test</scope>
</dependency>


JUnit 4


A quick note about JUnit 4. If your team/project is still using JUnit 4, you can use both JUnit 4 and JUnit 5 simultaneously. JUnit 5 provides a gentle migration path. Just include the following dependency. I also recommend reading the Migration Tips.

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <version>5.5.2</version>
    <scope>test</scope>
</dependency>

Now you are safe to write JUnit 5 tests without having to migrate all your existing JUnit 4 tests and be able to take advantage of the new @ParameterizedTest annotation.


Conclusion


Well hopefully I've convinced you of the power of Parameterized Tests and you'll look for the right opportunity to try them out. And keep in mind while these examples use Java, this technique should also be applicable to other languages. So do some research and see if your language provides them and if not maybe create your own. That's what we did before JUnit 5 was out and we weren't happy with the JUnit 4 way of doing Parameterized Tests.