Search

Dark theme | Light theme

October 1, 2024

Awesome AssertJ: Using Our Own Assertions Class

AssertJ already provides many useful assertions for all kind of types. But sometimes we want to define our own assertions for our own types. We can define new assertions by extending the AbstractAssert class In this class we add methods that will check the values of our type. The names of the methods can reflect the domain model of our type. This can make our tests more readable and understandable.

The abstract class AbstractAssert has a constructor that takes two arguments. We must pass the object we want to test and the class of the assertion class. The object that is passed is assigned to the property actual of the class. It is good practice to add a static method assertThat to the class that will return a new instance of the assertion class. Next we can add our own methods to the class. Within these methods we write the code to assert the values of our type. If the assertion would fail we can use the failWithMessage method to provide a message to the user.

AbstractAssert has also useful methods like isNotNull and isNotEqualTo that we can use in our assertions.

In the following example we write a custom assertion class for the type Pirate. The class Pirate has a nested object of type Ship. We define these with the following two records:

// File: mrhaki/Ship.java
package mrhaki;

public record Ship(String name, String type, int crewSize) {}
// File: mrhaki/Pirate.java
package mrhaki;

public record Pirate(String name, String rank, Ship ship) {}

Next we create our custom assertion class PirateAssert. We extend the class AbstractAssert, add a static method assertThat and the two assertion methods hasName and belongsOnShipWithName:

// File: mrhaki/PirateAssert.java
package mrhaki;

import org.assertj.core.api.AbstractAssert;

import java.util.Objects;

/**
 * Custom assertion methods for a Pirate instance.
 */
public class PirateAssert extends AbstractAssert<PirateAssert, Pirate> {
    protected PirateAssert(Pirate pirate) {
        super(pirate, PirateAssert.class);
    }

    /**
     * Method to create a new instance of PirateAssert, using the
     * same naming as standard AssertJ methods.
     * @param pirate Pirate instance to assert.
     * @return New PirateAssert instance.
     */
    public static PirateAssert assertThat(Pirate pirate) {
        return new PirateAssert(pirate);
    }

    /**
     * Check if the given name is equal to the actual
     * pirate's name.
     * @param name Expected name of the pirate.
     * @return Current instance for fluent API.
     */
    public PirateAssert hasName(String name) {
        // First check the actual Pirate instance is not null.
        isNotNull();

        // The Pirate instance passed in the assertThat method
        // is assigned to a variable with the name actual.
        // For comparison, we need to use that name.
        if (!Objects.equals(actual.name(), name)) {
            // Create an assertion failure with a message
            // that will be shown when the name is not equal to the actual name.
            failWithMessage("Expected pirate's name to be <%s> but was <%s>", name, actual.name());
        }

        // For fluent API usage we return this.
        return this;
    }

    /**
     * Check if the given name is equal to the name of the ship
     * the pirate belongs to.
     * @param name Expected name of the ship.
     * @return Current instance for fluent API.
     */
    public PirateAssert belongsOnShipWithName(String name) {
        isNotNull();

        if (Objects.isNull(actual.ship())) {
            failWithMessage("Expected pirate to belong on ship with name <%s> but was not found on any ship", name);
        }

        if (!Objects.equals(actual.ship().name(), name)) {
            failWithMessage("Expected pirate to belong on ship with name <%s> but was <%s>", name, actual.ship().name());
        }

        return this;
    }
}

In the following test we use the custom assertion class PirateAssert to assert the values of a Pirate instance:

// File: mrhaki/CustomPirateAssertion.java
package mrhaki;

import org.junit.jupiter.api.Test;

import static mrhaki.PirateAssert.assertThat;

public class CustomPirateAssertion {

    @Test
    void checkPirate() {
        // given
        Ship ship = new Ship("Black Pearl", "Galleon", 100);
        Pirate pirate = new Pirate("Jack Sparrow", "Captain", ship);


        // expect
        assertThat(pirate).hasName("Jack Sparrow")
                          .belongsOnShipWithName("Black Pearl");
    }
}

Written with AssertJ 3.26.3.

September 30, 2024

Awesome AssertJ: Comparing Objects Recursively

To compare nested objects we can use the usingRecursiveComparison() method in AssertJ. We can set up the nested objects with values we expect, invoke a method that would return the actual nested objects, and then use the usingRecursiveComparison() method to compare the actual nested objects with the expected nested objects. This is a very clean way to compare nested objects. Also when we would add a new property to the nested objects our test would fail as we didn’t use that new property yet for our expected nested objects.

In the following example test we use the usingRecursiveComparison() method to compare actual nested objects with the expected nested objects. Our nested objects are of type Pirate and Ship.

// File: mrhaki/Ship.java
package mrhaki;

public record Ship(String name, String type, int crewSize) {}
// File: mrhaki/Pirate.java
package mrhaki;

public record Pirate(String name, String rank, Ship ship) {}

This is our class we want to test. The class PirateShipCreator creates the nested objects we want to write assertions for.

// File: mrhaki/PirateShipCreator.java
package mrhaki;

public class PirateShipCreator {
    public static Pirate createJackSparrow() {
        return new Pirate("Jack Sparrow", "Captain");
    }

    public static Pirate createDavyJones() {
        return new Pirate("Davy Jones", "Captain", createBlackPearl());
    }

    private static Ship createBlackPearl() {
        return new Ship("Black Pearl", "Galleon", 100, createJackSparrow());
    }

    private static Ship createFlyingDutchmain() {
        return new Ship("Flying Dutchman", "Ghost ship", 199, createDavyJones());
    }
}

The test class PirateShipCreatorTest uses the usingRecursiveComparison() method to compare actual nested objects with the expected nested objects:

// File: mrhaki/PirateShipCreatorTest.java
package mrhaki;

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

public class PirateShipCreatorTest {

    @Test
    public void testPirateEquality() {
        // given
        Ship expectedShip = new Ship("Black Pearl", "Galleon", 100);
        Pirate expectedPirate = new Pirate("Jack Sparrow", "Captain", expectedShip);

        // when
        Pirate actualPirate = PirateShipCreator.createJackSparrow();

        // then
        // assert equality using recursive comparison
        assertThat(actualPirate)
            .usingRecursiveComparison()
            .isEqualTo(expectedPirate);
    }
}

If we want to ignore a property we can use the ignoringFields(String…​) method. Or if we want to ignore properties of a certain type we can use the ignoringFieldsOfTypes(Class<?>…​) method. This can be very useful for properties that store dates we cannot setup property in our tests.

Instead of ignoring fields we can also specify which fields we want to compare with the comparingOnlyFields(String…​) method. And there is a comparingOnlyFieldsOfTypes(Class<?>…​) method to specify which fields of a certain type we want to compare.

In the following tests we use all four methods to ignore or include fields in our comparison.

// File: mrhaki/PirateShipCreatorTest.java
package mrhaki;

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

public class PirateShipCreatorTest {

    @Test
    public void testPirateEqualityIgnoringShipCrewSize() {
        // given
        Ship expectedShip = new Ship("Flying Dutchman", "Ghost Ship", 100);
        Pirate expectedPirate = new Pirate("Davy Jones", "Captain", expectedShip);

        // when
        Pirate actualPirate = PirateShipCreator.createDavyJones();

        // then
        // assert equality using recursive comparison, ignoring crew size
        assertThat(actualPirate)
            .usingRecursiveComparison()
            .ignoringFields("ship.crewSize")
            .isEqualTo(expectedPirate);
    }

    @Test
    public void testPirateEqualityIgnoringIntegerFields() {
        // given
        Ship expectedShip = new Ship("Flying Dutchman", "Ghost Ship", 100);
        Pirate expectedPirate = new Pirate("Davy Jones", "Captain", expectedShip);

        // when
        Pirate actualPirate = PirateShipCreator.createDavyJones();

        // then
        // assert equality using recursive comparison, ignoring integer fields
        assertThat(actualPirate)
            .usingRecursiveComparison()
            .ignoringFieldsOfType(Integer.class)
            .isEqualTo(expectedPirate);
    }

    @Test
    public void testPirateEqualityComparingSelectedFields() {
        // given
        Ship expectedShip = new Ship("Flying Dutchman", "Ghost Ship", 100);
        Pirate expectedPirate = new Pirate("Davy Jones", "Captain", expectedShip);

        // when
        Pirate actualPirate = PirateShipCreator.createDavyJones();

        // then
        // assert equality using recursive comparison, comparing only selected fields
        assertThat(actualPirate)
            .usingRecursiveComparison()
            .comparingOnlyFields("name", "rank", "ship.name", "ship.type")
            .isEqualTo(expectedPirate);
    }

    @Test
    public void testPirateEqualityComparingSelectedTypeOfFields() {
        // given
        Ship expectedShip = new Ship("Flying Dutchman", "Ghost Ship", 100);
        Pirate expectedPirate = new Pirate("Davy Jones", "Captain", expectedShip);

        // when
        Pirate actualPirate = PirateShipCreator.createDavyJones();

        // then
        // assert equality using recursive comparison,
        // comparing only fields of type String and Ship
        assertThat(actualPirate)
            .usingRecursiveComparison()
            .comparingOnlyFieldsOfTypes(String.class, Ship.class)
            .isEqualTo(expectedPirate);
    }
}

Written with AssertJ 3.26.3.

May 21, 2024

Clojure Goodness: Extending is Macro With Custom Assertions

The is macro in the clojure.test namespace can be used to write assertions about the code we want to test. Usually we provide a predicate function as argument to the is macro. The prediction function will call our code under test and return a boolean value. If the value is true the assertion passes, if it is false the assertion fails. But we can also provide a custom assertion function to the is macro. In the clojure.test package there are already some customer assertions like thrown? and instance?. The assertions are implemented by defining a method for the assert-expr multimethod that is used by the is macro. The assert-expr multimethod is defined in the clojure.test namespace. In our own code base we can define new methods for the assert-expr multimethod and provide our own custom assertions. This can be useful to make tests more readable and we can use a language in our tests that is close to the domain or naming we use in our code.

The implementation of the custom assertion should call the function do-report with a map containing the keys :type, :message, :expected and :actual. The :type key can have the values :fail or :pass. Based on the code we write in our assertion we can set the value correctly. Mostly the :message key will have the value of the message that is defined with the is macro in our tests. The keys :expected and :actual should contain reference to what the assertion expected and the actual result. This can be a technical reference, but we can also make it a human readable reference.

In the following example we implement a new customer assertion jedi? that checks if a given name is a Jedi name. The example is based on an example that can be found in the AssertJ documentation.

(ns mrhaki.test
  (:require [clojure.test :refer [deftest is are assert-expr]]))

(defmethod assert-expr 'jedi?
  "Assert that a given name is a Jedi."
  [msg form]
  `(let [;; We get the name that is the second element in the form.
         ;; The first element is the symbol `'jedi?`.
         name# ~(nth form 1)
         ;; We check if the name is part of a given set of Jedi names.
         result# (#{"Yoda" "Luke" "Obiwan"} name#)
         ;; We create an expected value that is used in the assertion message.
         expected# (str name# " to be a jedi.")]
     (if result#
       (do-report {:type     :pass
                   :message  ~msg,
                   :expected expected#
                   :actual   (str name# " is actually a jedi.")})
       (do-report {:type     :fail
                   :message  ~msg,
                   :expected expected#
                   :actual   (str name# " is NOT a jedi.")}))
     result#))

;; We can use our custom assertion in our tests.
(deftest jedi
  (is (jedi? "Yoda")))

;; The custom assertion can also be used with
;; the are macro as it will expand into multiple
;; is macro calls.
(deftest multiple-jedi
  (are [name] (jedi? name)
    "Yoda" "Luke" "Obiwan"))

;; The following test will fail, so we can
;; see failure message with the :expected and :actual values.
(deftest fail-jedi
  (is (jedi? "R2D2") "Is it?"))

If we run our failing test we see in the output that the assertion message is using our definition of the expected and actual values:

...
 expected: "R2D2 to be a jedi."
   actual: "R2D2 is NOT a jedi."
...

Written with Clojure 1.11.3.

May 19, 2024

Clojure Goodness: Combine Multiple Test Cases With are

The clojure.test namespace has the are macro that allows us to combine multiple test cases for the code we want to test, without having to write multiple assertions. We can provide multiple values for a function we want to test together with the expected values. Then then macro will expand this to multiple expressions with the is macro where the real assertion happens. Besides providing the data we must also provide the predicate where we assert our code under test. There is a downside of the are macro and that is that in case of assertion failures the line numbers in the error message could be off.

The first argument of the are macro is a vector with symbols that represent the names of the variables we want to use in the predicate. The second argument is the predicate where we write our assertion for the code under test. The remaining arguments are values for the symbols in the first argument. We can provide multiple sets of values for the symbols in the first argument, and the are macro will use this when the macro is expanded into multiple expressions.

In the next example we use the are macro to test the full-name function.

(ns mrhaki.test
  (:require [clojure.test :refer [deftest are]]
            [clojure.string :as str]))

;; Function we want to test.
(defn is-palidrome?
  [s]
  (= s (str/reverse s)))

(deftest palidrome
  (are
   ;; Vector with symbol used in test expression
   [s]
   ;; Test expression where we test the is-palidrome? function
   (true? (is-palidrome? s))
    ;; Data for the input symbol s
    "radar"
    "kayak"
    "racecar"
    "madam"
    "refer"
    "step on no pets"))

In the following example we use a test expression where we also use an expected value that is provided with the input data:

(ns mrhaki.test
  (:require [clojure.test :refer [deftest are]]))

;; Function we want to test.
(defn full-name
  "Returns a full name"
  [first-name last-name]
  (str first-name " " last-name))

(deftest sample
  (are
   [first-name last-name result]
   (= (full-name first-name last-name) result)
    "Fat" "Tony" "Fat Tony"
    "Krusty" "the Clown" "Krusty the Clown"))

Written with Clojure 1.11.3.

April 21, 2024

Clojure Goodness: Pretty Printing Collection Of Maps

The namespace clojure.pprint has some useful function to pretty print different data structures. The function print-table is particularly useful for printing a collection of maps, where each map represents a row in the table, and the keys of the maps represent the column headers. The print-table function accepts the collection as argument and prints the table to the console (or any writer that is bound to the *out* var). We can also pass a vector with the keys we want to include in the table. Only the keys we specify are in the output. The order of the keys in the vector we pass as argument is also preserved in the generated output.

In the following example code we use the print-table function to print some conference data that is stored in a vector of maps. We use the function with-out-str to capture the output of print-table so we can compare it with our expected result. In the example we first call print-table with the full conferences vector, and then we call it again with a vector of specific keys we want to include in the output.

(ns mrhaki.sample.print-table
  (:require [clojure.pprint :refer [print-table]]
            [clojure.test :refer [is]])
  (:import [java.io StringWriter]))

;; Vector with maps representing conference information.
(def conferences [{:name "Javaland" :location "Nürburgring" :country "Germany"}
                  {:name "JFall" :location "Ede" :country "The Netherlands"}
                  {:name "Full Stack Conference" :location "Nieuwegein" :country "The Netherlands"}])

;; Using print-table we get a nicely formatted table with all
;; rows from our conferences vector.
;; Each key name is a column header.
;; We use with-out-str function to capture the output
;; of the print-table function as String value.
(is (= "
|                 :name |   :location |        :country |
|-----------------------+-------------+-----------------|
|              Javaland | Nürburgring |         Germany |
|                 JFall |         Ede | The Netherlands |
| Full Stack Conference |  Nieuwegein | The Netherlands |
" (with-out-str (print-table conferences))))

;; Using print-table with a vector of keys we get a nicely formatted table
;; with all rows from our conferences vector.
;; But now the columns are only the keys we specified.
;; The order of the keys is also the order of the columns.
;; We use with-out-str function to capture the output
;; of the print-table function as String value.
(is (= "
|        :country |                 :name |
|-----------------+-----------------------|
|         Germany |              Javaland |
| The Netherlands |                 JFall |
| The Netherlands | Full Stack Conference |
" (with-out-str (print-table [:country :name] conferences))))

Written with Clojure 1.11.2.

March 25, 2024

Mastering Mockito: Returning Fresh Stream For Multiple Calls To Mocked Method

When we mock a method that returns a Stream we need to make sure we return a fresh Stream on each invocation to support multiple calls to the mocked method. If we don’t do that, the stream will be closed after the first call and subsequent calls will throw exceptions. We can chain multiple thenReturn calls to return a fresh Stream each time the mocked method is invoked. Or we can use multiple arguments with the thenReturn method, where each argument is returned based on the number of times the mocked method is invoked. So on the first invocation the first argument is returned, on second invocation the second argument and so on. This works when we know the exact number of invocations in advance. But if we want to be more flexible and want to support any number of invocations, then we can use thenAnswer method. This method needs an Answer implementation that returns a value on each invocation. The Answer interface is a functional interface with only one method that needs to be implemented. We can rely on a function call to implement the Answer interface where the function gets a InvocationOnMock object as parameter and returns a value. As the function is called each time the mocked method is invoked, we can return a Stream that will be new each time.

In the following example we use chained method calls using thenReturn and we use thenAnwer to support multiple calls to our mocked method temperature that returns a Stream of Double values:

package mrhaki;

import org.junit.jupiter.api.Test;

import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class MockReturnMultipleStreams {

    // Simple interface to return a stream of
    // temperature values for a given city.
    interface Weather {
        Stream<Double> temperature(String city);
    }

    // Simple class that uses Weather interface.
    static class SubjectUnderTest {

        private final Weather weather;

        SubjectUnderTest(Weather weather) {this.weather = weather;}

        public String weatherReport(String city) {
            // By invoking the methods celcius and fahrenheit we will
            // invoke the weather.temperature method twice.
            return String.format("The temperature in %s is %.1f degrees Celcius or %.1f degrees Fahrenheit.",
                                 city, celcius(city), fahrenheit(city));
        }

        private double celcius(String city) {
            return weather.temperature(city).findAny().get();
        }

        private double fahrenheit(String city) {
            return (celcius(city) * 9/5)  + 32;
        }
    }

    private final Weather weather = mock(Weather.class);
    private final SubjectUnderTest subjectUnderTest = new SubjectUnderTest(weather);

    @Test
    void shouldReturnCorrectWeatherReport() {
        // given

        // Return type of the mocked method temperature is a Stream.
        // On the first call in the subjectUnderTest instance the stream
        // is closed, so the second call will give an exception that
        // the stream is already been operated upon or closed.
        // To support the second call we need to return a new stream
        // with the same content.
        // If we need to support more calls than two we need
        // to add more thenReturn statements.
        // See the next test method for an example with thenAnswer
        // that supports multiple calls more easily.
        double temperature = 21.0;
        when(weather.temperature("Tilburg"))
                // First call
                .thenReturn(Stream.of(temperature))
                // Second call
                .thenReturn(Stream.of(temperature));

        // Alternative syntax:
        // when(weather.temperature("Tilburg"))
        //        .thenReturn(Stream.of(temperature), Stream.of(temperature));

        // when
        String result = subjectUnderTest.weatherReport("Tilburg");

        // then
        assertThat(result).isEqualTo("The temperature in Tilburg is 21,0 degrees Celcius or 69,8 degrees Fahrenheit.");
    }

    @Test
    void shouldReturnCorrectWeatherReport2() {
        // given

        // Return type of the mocked method temperature is a Stream.
        // On the first call in the subjectUnderTest instance the stream
        // is closed, so the second call will give an exception that
        // the stream is already been operated upon or closed.
        // To support the second call we can use thenAnswer method
        // which will return a fresh Stream on each call.
        // Now the number of calls is not limited, because on each
        // invocation a fresh Stream is created.
        when(weather.temperature("Tilburg"))
                .thenAnswer(invocationOnMock -> Stream.of(21.0));

        // when
        String result = subjectUnderTest.weatherReport("Tilburg");

        // then
        assertThat(result).isEqualTo("The temperature in Tilburg is 21,0 degrees Celcius or 69,8 degrees Fahrenheit.");
    }
}

Written with Mockito 3.12.4.

March 8, 2024

Gradle Goodness: Organizing Tasks Using The Task Container

A Gradle build file describes what is needed to build our Java project. We apply one or more plugins, configure the plugins, declare dependencies and create and configure tasks. We have a lot of freedom to organize the build file as Gradle doesn’t really care. So to create maintainable Gradle build files we need to organize our build files and follow some conventions. In this post we focus on organizing the tasks and see if we can find a good way to do this.

It is good to have a single place where all the tasks are created and configured, instead of having all the logic scattered all over the build file. The TaskContainer is a good place to put all the tasks. To access the TaskContainer we can use the tasks property on the Project object. Within the scope of the tasks block we can create and configure tasks. Now we have a single place where all the tasks are created and configured. This makes it easier to find the tasks in our project as we have a single place to look for the tasks.

Within the scope of the TaskContainer we can use a convention to put the task creation methods at the top of the TaskContainer block. And the task configuration methods are after the task creation in the TaskContainer block. The tasks that are created at the top of the TaskContainer scope can be referenced by configuration code for tasks later in the TaskContainer scope.

The following diagram shows the build file structure and an example of the implementation:

In the example Gradle build file for a Java project we organize the tasks in the TaskContainer using this convention:

plugins {
    java
}
...
tasks {
    // ----------------------------------------------
    // Task creation at the top of the container.
    // ----------------------------------------------

    // Register new task "uberJar".
    val uberJar by registering(Jar::class) {
        archiveClassifier = "uber"

        from(sourceSets.main.get().output)

        dependsOn(configurations.runtimeClasspath)
        from({
            configurations.runtimeClasspath.get()
                .filter { it.name.endsWith("jar") }
                .map { zipTree(it) }
        })
    }

    // ----------------------------------------------
    // Task configuration after task creation.
    // ----------------------------------------------

    // The output of the "uberJar" tasks is part of
    // the output of the "assemble" task.
    // We can refer to the "assemble" task directly
    // as it is added by the Java plugin.
    assemble {
        // We can refer to the task name that
        // we just created in our
        // tasks configuration block.
        dependsOn(uberJar)
    }

    // Configure tasks with type JavaCompile.
    withType<JavaCompile>().configureEach {
        options.compilerArgs.add("--enable-preview")
    }
}
...

Although Gradle doesn’t enforce us to use this convention it can be very helpful as build file authors to use it as it makes it easier to find the tasks in the project.

Written with Gradle 8.6.

March 6, 2024

IntelliJ HTTP Client: Parsing JSON Web Tokens

The IntelliJ HTTP Client is very useful for testing APIs. We can use Javascript to look at the response and write tests with assertions about the response. If an API returns a JSON Web Token (JWT), we can use a Javascript function to decode the token and extract information from it. For example we can then assert that fields of the token have the correct value. There is no built-in support in IntelliJ HTTP Client to decode a JWT, but we can write our own Javascript function to do it. We then use the function in our Javascript response handler to decode the token.

In the following HTTP request file we simulate a call to get an response with a field containing an JWT. We use the function decodeJwt from the file jwt-utils.js to decode the token and extract information from it.

### Simulate a call to get an response with a field containing an JWT.
POST https://examples.http-client.intellij.net/anything
Content-Type: application/json

// Original token before it is base64 encoded:
// {
//     "sub": "1234567890",
//     "upn": "hubert@mrhaki.com",
//     "name": "mrhaki",
//     "groups": ["Blogger"],
//     "iat": 1516239022
// }
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwidXBuIjoiaHViZXJ0QG1yaGFraS5jb20iLCJuYW1lIjoibXJoYWtpIiwiZ3JvdXBzIjpbIkJsb2dnZXIiXSwiaWF0IjoxNTE2MjM5MDIyfQ.9E2gYNFogs3K8pJH9JiJYISv403EtCm4tRzQWZi1CXM"
}

> {%
    import {decodeJwt} from './scripts/jwt-utils';

    // The token is in the response body and we get it
    // using the path `json.token`.
    // We store it as variable `token` so we can use in the next step.
    const token = decodeJwt(response.body.json.token);

    // We can write assertions on the token contents.
    client.test("Check fields in token", function () {
        client.assert(token.upn === "hubert@mrhaki.com");
        client.assert(token.name === "mrhaki");
        client.assert(token.groups.includes("Blogger"));
        client.assert(token.sub === "1234567890");
        client.assert(token.iat === 1516239022);
    });
%}

The function decodeJwt is defined in the file jwt-utils.js:

// File: ./scripts/jwt-utils.js
export function decodeJwt(token) {
    var base64EncodedPayload = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
    var jsonPayload = decodeURIComponent(decodeBase64(base64EncodedPayload)
        .split('')
        .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
        .join(''))
        // Remove any NUL characters at the end of the string.
        .replace(/\0+$/g, '');
    return JSON.parse(jsonPayload);
}

function decodeBase64(input) {
    const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    let output = "";
    let chr1, chr2, chr3;
    let enc1, enc2, enc3, enc4;
    let i = 0;
    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
    while (i < input.length) {
        enc1 = _keyStr.indexOf(input.charAt(i++));
        enc2 = _keyStr.indexOf(input.charAt(i++));
        enc3 = _keyStr.indexOf(input.charAt(i++));
        enc4 = _keyStr.indexOf(input.charAt(i++));
        chr1 = (enc1 << 2) | (enc2 >> 4);
        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
        chr3 = ((enc3 & 3) << 6) | enc4;
        output = output + String.fromCharCode(chr1);
        if (enc3 !== 64) {
            output = output + String.fromCharCode(chr2);
        }
        if (enc4 !== 64) {
            output = output + String.fromCharCode(chr3);
        }
    }
    return decodeURI(output);
}

Written with IntelliJ IDEA 2023.3.4.

February 25, 2024

Gradle Goodness: Using System Properties Lazily

It is good practice in Gradle to use lazy configuration. This makes builds faster as only configuration values are evaluated when needed. We should try to not let Gradle spend time on evaluating configuration values that will not be used. For example tasks that are not executed could still be configured by Gradle. If we make sure the configuration of these tasks is lazy we can save time.

Gradle gives us a lazy way to get the value of a Java system property. In our build script we can use the providers property of type ProviderFactory and the method systemProperty(String). This method returns a Provider<String> instance that can be used to get the value of a system property in a lazy way. The method systemProperty can also be used with a Provider<String> argument.

In the following example we register a task that prints the value of the Java system property user.name to the console. We use lazy configuration to make sure the value of the system property is only fetched when the task is executed.

tasks {
    register<PrintSystemProperty>("printSystemProperty") {
        // We can use providers.systemProperty(String)
        // to get the value of an Java system property
        // in a lazy way.
        // The argument can also be a Provider<String> type.
        // So at this point the value is not fetched yet,
        // only when the task is executed the actual value
        // of the system property "user.name" is fetched.
        systemProperty = providers.systemProperty("user.name")
    }
}

// Simple task to print the value of a Java system property.
abstract class PrintSystemProperty : DefaultTask() {
    @get:Input
    abstract val systemProperty: Property<String> // Use lazy property.

    @TaskAction
    fun printSystemPropertyValue() {
        // Only here we actually will get the value
        // for the system property.
        logger.quiet(systemProperty.get())
    }
}
$ ./gradlew printSystemProperty

> Task :printSystemProperty
mrhaki

BUILD SUCCESSFUL in 685ms
2 actionable tasks: 2 executed

Written with Gradle 8.6.

Gradle Goodness: Using Environment Variables Lazily

It is good practice in Gradle to use lazy configuration. This makes builds faster as only configuration values are evaluated when needed. We should try to not let Gradle spend time on evaluating configuration values that will not be used. For example tasks that are not executed could still be configured by Gradle. If we make sure the configuration of these tasks is lazy we can save time.

Gradle gives us a lazy way to get the value of an environment variable. In our build script we can use the providers property of type ProviderFactory and the method environmentVariable(String). This method returns a Provider<String> instance that can be used to get the value of an environment variable in a lazy way.

In the following example we register a task that prints the value of the environment variable USER. We use lazy configuration to make sure the value of the environment variable is only fetched when the task is executed.

tasks {
    register<PrintEnvironmentVariable>("printEnvironmentVariable") {
        // We can use providers.environmentVariable(String)
        // to get the value of an environment variable
        // in a lazy way.
        // The argument can also be a Provider<String> type.
        // So at this point the value is not fetched yet,
        // only when the task is executed the actual value
        // of the environment variable "USER" is fetched.
        environmentVariable = providers.environmentVariable("USER")
    }
}

// Simple task to print the value of an environment variable.
abstract class PrintEnvironmentVariable : DefaultTask() {
    @get:Input
    abstract val environmentVariable: Property<String> // Use lazy property.

    @TaskAction
    fun printEnvironmentVariable() {
        // Only here we actually will get the value
        // for the environment variable.
        logger.quiet(environmentVariable.get())
    }
}

When we execute the tasks we see the value of the environment variable USER:

$ ./gradlew printEnvironmentVariable

> Task :printEnvironmentVariable
mrhaki

BUILD SUCCESSFUL in 599ms
2 actionable tasks: 2 executed

Written with Gradle 8.6.