Search

Dark theme | Light theme

July 22, 2023

Java Joy: Using mapMulti Method Of The Stream API

Since Java 16 we can use the method mapMulti(BiConsumer) of the Stream API. This method allows us to map each element of the stream to multiple elements. We can also do that with the flatMap(Function) method, but if we want to map a limited set of elements, mapMulti is more convenient. Internally a shared stream is used and we don’t have the cost of creating a new stream for each element. Another use case is if the logic to map an element to multiple elements is complex and is hard to implement by returning a stream. Then mapMulti allows us to write that logic in a BiConsumer instead of a Function.

In the following code we use the mapMulti method in several examples:

package mrhaki.stream;

import javax.print.attribute.HashPrintServiceAttributeSet;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MapMulti {

    public static void main(String[] args) {
        // We want to return a stream of string values 
        // and the uppercase variant
        // if the original element has the letter o.
        assert Stream.of("Java", "Groovy", "Clojure")
                     .mapMulti((language, downstream) -> {
                         if (language.contains("o")) {
                             downstream.accept(language);
                             downstream.accept(language.toUpperCase());
                         }
                     })
                     .toList()
                     .equals(List.of("Groovy", "GROOVY", "Clojure", "CLOJURE"));

        // Same logic implemented with flatMap.
        assert Stream.of("Java", "Groovy", "Clojure")
                     .filter(language -> language.contains("o"))
                     .flatMap(language -> Stream.of(language, language.toUpperCase()))
                     .toList()
                     .equals(List.of("Groovy", "GROOVY", "Clojure", "CLOJURE"));


        // Helper record to store a name and set of language names.
        record Developer(String name, List<String> languages) {}

        // Number of sample developers that work with different languages.
        var hubert = new Developer("mrhaki", List.of("Java", "Groovy", "Clojure"));
        var java = new Developer("java", List.of("Java"));
        var clojure = new Developer("clojure", List.of("Clojure"));
        var groovy = new Developer("groovy", List.of("Groovy"));

        record Pair(String name, String language) {}

        // Let's find all developers that have Java in their
        // set of languages and return a new Pair
        // object with the name of the developer and a language.
        assert Stream.of(hubert, java, clojure, groovy)
                     // We can explicitly state the class that will be 
                     // in the downstream of the compiler cannot
                     // deduct it using a <...> syntax.
                     .<Pair>mapMulti((developer, downstream) -> {
                         var languages = developer.languages();
                         if (languages.contains("Java")) {
                             for (String language : developer.languages()) {
                                 downstream.accept(new Pair(developer.name(), language));
                             }
                         }
                     })
                     .toList()
                     .equals(List.of(new Pair("mrhaki", "Java"),
                                     new Pair("mrhaki", "Groovy"),
                                     new Pair("mrhaki", "Clojure"),
                                     new Pair("java", "Java")));

        // Same logic using filter and flatMap.
        assert Stream.of(hubert, java, clojure, groovy)
                     .filter(developer -> developer.languages().contains("Java"))
                     .flatMap(developer -> developer.languages()
                                                    .stream()
                                                    .map(language -> new Pair(developer.name(), language)))
                     .toList()
                     .equals(List.of(new Pair("mrhaki", "Java"),
                                     new Pair("mrhaki", "Groovy"),
                                     new Pair("mrhaki", "Clojure"),
                                     new Pair("java", "Java")));
        
        
        // We want to expand each number to itself and its square root value
        // and we muse mapMultiToInt here.
        var summaryStatistics = Stream.of(1, 2, 3)
                                      .mapMultiToInt((number, downstream) -> {
                                          downstream.accept(number);
                                          downstream.accept(number * number);
                                      })
                                      .summaryStatistics();

        assert summaryStatistics.getCount() == 6;
        assert summaryStatistics.getSum() == 20;
        assert summaryStatistics.getMin() == 1;
        assert summaryStatistics.getMax() == 9;
    }
}

Written with Java 20.

July 17, 2023

Awesome AssertJ: Writing Assertions For Optional

For a lot of types AssertJ has special assertion methods. Also for the type Optional. If we want to assert an Optional value we can use several methods that AssertJ provides. For example to check if an Optional is present we can use isPresent() or the alias isNotEmpty(). To check if the Optional is empty we can use isEmpty() or the alias isNotPresent(). Checking the value of an Optional (if it is indeed set) can be done with hasValue() or contains(). For more fine grained assertions on the value we can use hasValueSatisfying(Condition) or hasValueSatisfying(Consumer). With the map(Function) and flatMap(Function) methods we can map the Optional, if not empty, to another value and assert that value.

In the following example we see assertion methods for an Optional instance:

package mrhaki;

import org.assertj.core.api.Condition;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;

import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;

class OptionalAssertions {
    
    @Test 
    void checkOptionalPresent(){
        assertThat(Optional.of("Awesome AssertJ"))
                // Check if Optional has a value.
                .isPresent()
                
                // Or we use the alias isNotEmpty().
                .isNotEmpty();
    }
    
    @Test 
    void checkOptionalNotPresent() {
        assertThat(Optional.empty())
                // Check if Optional is empty.
                .isEmpty()
                
                // Or we use the aliase isNotPresent().
                .isNotPresent();
    }
    
    @Test
    void checkValueOfOptional() {
        assertThat(Optional.of("Awesome AssertJ"))
                // We can check the value with hasValue(...).
                // We don't have to call get() ourselves.
                .hasValue("Awesome AssertJ")
                
                // Or we use the alias contains(...).
                .contains("Awesome AssertJ");
    }
    @Test
    void checkGetOfOptional() {
        // We can use get() to chain more assertions,
        // but we can only use generic Object assertions.
        assertThat(Optional.of("Awesome AssertJ"))
                .get()
                // We can only use Object assertions.
                .isEqualTo("Awesome AssertJ");

        // If we use get(InstanceOfAssertFactory) we get
        // back an object to use assertion methods for the 
        // given type.
        // The interface InstanceOfAssertFactories has a lot of 
        // useful constants to force the type returned by the Optional,
        // so we can use the assertions for that type.
        assertThat(Optional.of("Awesome AssertJ"))
                .get(InstanceOfAssertFactories.STRING)
                // Now we can use String assertions:
                .startsWith("Awesome")
                .endsWith("AssertJ");
    }

    @Test
    void checkValueSatisfyingOfConditional() {
        // We can use hasValueSatisfying(Consumer)
        // with the instance that is wrapped in the Optional
        // for more fine grained assertion.
        // Also, we can use assertions for the type wrapped
        // in the Optional.
        assertThat(Optional.of("Awesome AssertJ"))
                .hasValueSatisfying(s -> {
                    assertThat(s)
                            .startsWith("Awesome")
                            .endsWith("AssertJ");
                });
    }
    
    @Test
    void checkObjectValueOfOptionalWithCondition() {
        // Record to store properties of a framework.
        record Framework(String name, boolean awesome) {}

        // We can write a Condition that checks if the awesome
        // property is set to true.
        var isAwesomeFramework = new Condition<Framework>(Framework::awesome, "Framework is awesome!");

        // And with hasValueSatisfying we can use the Condition.
        assertThat(Optional.of(new Framework("AssertJ", true)))
                .hasValueSatisfying(isAwesomeFramework);
    }

    @Test
    void checkTransformedValueOfOptional() {
        // We can use map(...) to transform the value
        // of the optional (if present)
        // and write assertions for the transformed value.
        assertThat(Optional.of("Awesome AssertJ"))
                .map(String::toUpperCase)
                .hasValue("AWESOME ASSERTJ");

        // Record to store framework data with an optional type.
        record Framework(String name, Optional<Boolean> awesome) {}
        
        // With flatMap(...) we can transform the value
        // of the optional (if present) to another Optional
        // and write assertions for this transformed Optinal value.
        assertThat(Optional.of(new Framework("Awesome AssertJ", Optional.of(true))))
                .flatMap(Framework::awesome)
                .isPresent()
                .hasValue(true);
    }

    @Test
    void checkInstanceOfOptional() {
        // With containsInstanceOf we can write an assertion
        // to check the type that is contained in the Optional.
        assertThat(Optional.of("Awesome AssertJ"))
                .containsInstanceOf(String.class);
    }
}

Written with AssertJ 3.24.2.

July 12, 2023

Awesome AssertJ: Assertions For An URL Object

AssertJ has a lot of custom assertion methods for different types. For example to assert an URL object AssertJ gives us some specific methods. We can check for different components of the URL instance with different methods. For example we can check if the protocol is equal to the protocol we expect with hasProtocol(String). Similarly we can write assertions for the host, port, authority, path and anchor. To assert query parameters we can use hasQueryParameter(String) to check if query parameter is set and with hasQueryParameter(String, String) we can check if the query parameter has an expected value. To check the whole query string we can use hasQueryString(String).

Each of the assertion methods also has version to assert a component is not present. For example hasNoQuery() to assert a query is not defined for an URL.

In the following example we use the different assertion methods for an URL instance:

package mrhaki;

import org.junit.jupiter.api.Test;

import java.net.MalformedURLException;
import java.net.URL;

import static org.assertj.core.api.Assertions.assertThat;

class UrlChecks {

    @Test
    void checkUrl() throws MalformedURLException {
        assertThat(new URL("https://www.mrhaki.com:80/blogs/index.html#groovy-goodness"))

                // We can check the protocol.
                .hasProtocol("https")

                // We can check the host name.
                .hasHost("www.mrhaki.com")

                // We can check the port if it is part of the URL.
                .hasPort(80)

                // We can check the authority (host + port).
                .hasAuthority("www.mrhaki.com:80")

                // We can check the path part of the URL.
                .hasPath("/blogs/index.html")

                // We can check the anchor part.
                .hasAnchor("groovy-goodness");
    }

    @Test
    void checkQueryParameters() throws MalformedURLException {
        assertThat(new URL("https://www.mrhaki.com/blogs/?tag=Groovy&text=Goodness"))

                // We can check the full query string.
                .hasQuery("tag=Groovy&text=Goodness")

                // We can check if a query parameter is defined.
                .hasParameter("tag")
                .hasParameter("text")

                // We can check if a query parameter with a given value is set.
                .hasParameter("tag", "Groovy")
                .hasParameter("text", "Goodness")

                // We can verify with another URL after the query parameters are sorted.
                .isEqualToWithSortedQueryParameters(new URL("https://www.mrhaki.com/blogs/?text=Goodness&tag=Groovy"));
    }

    @Test
    void checkUserInfo() throws MalformedURLException {
        assertThat(new URL("https://user:password@www.mrhaki.com/"))

                // We can check the user info part of the URL.
                .hasUserInfo("user:password");
    }

    @Test
    void checkUrlNegated() throws MalformedURLException {
        // We can also check for the non-presence of components of the URL.
        assertThat(new URL("https://www.mrhaki.com"))
                .hasNoPort()
                .hasNoPath()
                .hasNoQuery()
                .hasNoParameters()
                .hasNoParameter("tag")
                .hasNoParameter("tag", "Groovy")
                .hasNoAnchor()
                .hasNoUserInfo();

        assertThat(new URL("file:///Users/mrhaki"))
                .hasNoHost();
    }
}

Written with AssertJ 3.24.2.

July 5, 2023

Awesome AssertJ: Check Base64 Encoded Strings

AssertJ has some nice methods to verify string values. If we want to verify a string value is Base64 encoded we can use the isBase64String() method. We can leave out the padding of the value as it is optional. With the method asBase64Decoded() we can decode the value and write our assertions for the decoded value. The method asBase64Decoded() returns a byte[] object and we can use the asString() to convert it into a string value again.

In the following example we use the isBase64() and asBase64Decoded() methods:

package mrhaki;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

public class StringBase64 {

    @Test
    void checkValueIsBase64Encoded() {
        // We can simply check if the value is Base64 encoded
        // with the method isBase64().
        assertThat("QXNzZXJ0SiBiZSBBd2Vzb21lLCBtYXRleSE=").isBase64();

        // Padding (=) can be omitted from the Base64 encoded value.
        assertThat("QXNzZXJ0SiBiZSBBd2Vzb21lLCBtYXRleSE").isBase64();

        // If we want to check a value is NOT Base64 encoded we
        // need to check an AssertionError is thrown.
        assertThatExceptionOfType(AssertionError.class)
                .isThrownBy(() -> assertThat("AssertJ is Awesome").isBase64())
                .withMessageContaining("Expecting \"AssertJ is Awesome\" to be a valid Base64 encoded string");
    }

    @Test
    void checkBase64EncodedValue() {
        // We can use asBase64Decoded() to get a byte[] result
        // and apply our assertions.
        assertThat("QXNzZXJ0SiBiZSBBd2Vzb21lLCBtYXRleSE=")
                .asBase64Decoded().asString()
                .isEqualTo("AssertJ be Awesome, matey!");

        // Also here the padding (=) can be omitted.
        assertThat("QXNzZXJ0SiBiZSBBd2Vzb21lLCBtYXRleSE")
                .asBase64Decoded().asString()
                .isEqualTo("AssertJ be Awesome, matey!");

        assertThat("QXNzZXJ0SiBiZSBBd2Vzb21lLCBtYXRleSE=")
                .asBase64Decoded().asString()
                .isNotEqualTo("AssertJ is Awesome!");
    }
}

Written with AssertJ 3.24.2.

Awesome AssertJ: Use String Template To Verify String Value

To compare string values we can use the isEqualTo(String) method in AssertJ. But if we want to verify that a string contains a certain variable value we can use string templates. This makes the assertion more readable as we can see what value we expect in the string. To use string templates we must the method isEqualTo(String, Object…​). The first argument is the string template and the following arguments will be the actual values that should be used in the template. Actually the String.format(String, Object…​) method is used behind the scenes to format the string template, but we don’t have to clutter our assertions with that call.

In the following example we see how we can use string templates to verify string values:

package mrhaki;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static org.assertj.core.api.Assertions.assertThat;

public class StringIsEqualToTemplate {

    @Test
    void verifyStringIsEqualTo() {
        // We can use a template to define the format of the String.
        // If we do actually String.format() is used to create
        // our expected value.
        assertThat("AssertJ is awesome!").isEqualTo("%s is %s!", "AssertJ", "awesome");
    }

    // Using a template can be very useful for example in parameterized tests.
    @ParameterizedTest
    @ValueSource(strings = {"AssertJ", "Spock"})
    void verifyStringFormat(String libraryName) {
        // given
        record Library(String name) {
            @Override
            public String toString() {
                return name;
            }
        }
        var library = new Library(libraryName);

        // expect
        assertThat(library.name() + " is awesome").isEqualTo("%s is awesome", library);
    }

    @Test
    void verifyStringIsNotEqualTo() {
        // There is no method implementation for isNotEqualTo where we can define
        // the format as first argument. But we can still use String.format
        // ourselves to achieve the same functionality.
        assertThat("AssertJ is awesome!").isNotEqualTo(String.format("%s is %s!", "Spock", "awesome"));
    }
}

Written with AssertJ 3.24.2.

July 3, 2023

Awesome AssertJ: Use returns To Verify An Object Using Functions

With the returns method in AssertJ we can verify an object using a function. This allows us to verify an object in a very flexible way. We can chain multiple returns method calls to verify multiple aspects of our object. The first argument of the returns method is the expected value of the function call. And the second argument is a function that calls a method on the object we want to verify. A simple function call would be a method reference using the class of the object. But we can also write our own function, where the argument of the function is actual object we are writing the assertion for. To verify the function doesn’t return an expected value we can use the method doesNotReturn.
We can also pass the function to the from method, available in the Assertions class. It can make the assertion more readeable as we can now read the code as: we expect the following value from calling this function.

In the following example we use the returns and doesNotReturn methods in several use cases:

package mrhaki;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.from;

class Returns {

    // Pirate record we will use for tests.
    record Pirate(String name, String ship, int age) {}

    @Test
    void checkReturnValueMethodReference() {
        // With returns() we can specify the expected value
        // from a function call.
        // We can use a method references to easily
        // get a return value we want to verify.
        assertThat(new Pirate("Jack Sparrow", "Black Pearl", 40))
                .returns("Jack Sparrow", Pirate::name)
                .returns("Black Pearl", Pirate::ship)
                .returns(40, Pirate::age);
    }

    @Test
    void checkValueIsNotReturned() {
        // Instead of using returns() we can use doesNotReturn()
        // to verify the function doesn't return an expected value.
        assertThat(new Pirate("Will Turner", "Flying Dutchman", 35))
                .doesNotReturn("Jack Sparrow", Pirate::name)
                .doesNotReturn("Black Pearl", Pirate::ship)
                .doesNotReturn(40, Pirate::age);
    }

    @Test
    void checkReturnValueFrom() {
        // We can use the Assertions.from() method to add
        // some readability to our assertions.
        // We simply pass the function that should return our
        // expected value to the from() method.
        assertThat(new Pirate("Jack Sparrow", "Black Pearl", 40))
                .returns("Jack Sparrow", from(Pirate::name))
                .returns("Black Pearl", from(Pirate::ship))
                .returns(40, from(Pirate::age));
    }

    @Test
    void checkReturnValueFromFunction() {
        // Instead of using method references as functions
        // like in the first test,
        // we can use custom functions as well.
        // The type of the function argument is the object
        // we write the assertions for.
        assertThat(new Pirate("Jack Sparrow", "Black Pearl", 40))
                .returns(true, p -> p.name().startsWith("Jack"))
                .returns("black-pearl", p -> p.ship().toLowerCase().replace(" ", "-"))
                // We can also from() here.
                .returns(12, from(p -> p.name().length()))
                .returns(false, p -> p.age() > 50);
    }
}

Written with AssertJ 3.24.2.

June 30, 2023

Awesome AssertJ: Check String Starts Or Ends With A Given Value

Writing assertions using the nice fluent API of AssertJ is a joy. Besides some of the basic assertions like isEqualTo AssertJ also has specific assertions for specific types. For example if we want write an assertion to check if a String value starts or ends with an expected value we can use the startsWith(String) or endsWith(String) methods. If we don’t care that a character is upper or lower case we can also use startsWithIgnoringCase(String) or endsWithIgnoringCase(String). Each of the methods also has a counterpart method to check the String value doesn’t start or end with an expected value. For example we can use doesNotStartWith(String) to assert a value does not start with the expected value.

Instead of using the specific startsWith…​(String) or endsWith…​(String) methods we can also use the more generic matches method. Now we need to specify a Pattern or Predicate that matches our requirement. This give some more flexbility but is also more verbose. To test there is not a match we can use the doesNotMatch method.

In the following example we write assertions to check if a String value starts with an expected value:

package mrhaki;

import org.junit.jupiter.api.Test;
import java.util.regex.Pattern;

import static org.assertj.core.api.Assertions.assertThat;

class StringStartsWith {

    @Test
    void testStringStartsWith() {
        assertThat("Awesome AssertJ").startsWith("Awesome");

        // We can also check ignoring the casing of the String value.
        assertThat("Awesome AssertJ").startsWithIgnoringCase("aweSome");
    }

    @Test
    void testStringDoesNotStartWith() {
        // Or we check a String value doesn't start with an expected value.
        assertThat("Amazing Assertj").doesNotStartWith("Awesome");

        // We can also check ignoring the casing of the String value.
        assertThat("Amazing Assertj").doesNotStartWithIgnoringCase("aweSome");
    }

    @Test
    void testStringStartsWithRegex() {
        // We can use regular expressions as well, but it is more verbose.
        assertThat("Awesome AssertJ").matches(Pattern.compile("^Awesome.*"));

        // Also here we can ignore casing.
        assertThat("Awesome AssertJ").matches(Pattern.compile("^awesome.*", Pattern.CASE_INSENSITIVE));

        // And check String value doesn't start with expected value.
        assertThat("Amazing AssertJ").doesNotMatch(Pattern.compile("^Awesome.*"));
    }

    @Test
    void testStringStartsWithPredicate() {
        // We can use a Predicate as well, but it is more verbose.
        assertThat("Awesome AssertJ").matches(s -> s.startsWith("Awesome"));

        // We are more flexible as we can use for example specify an offset.
        assertThat("Awesome AssertJ").matches(s -> s.startsWith("some", 3));

        // If we want to ignore casing we have to first convert our value.
        assertThat("Awesome AssertJ").matches(s -> s.toLowerCase().startsWith("awesome"));

        // And check String value doesn't start with expected value.
        assertThat("Amazing AssertJ").matches(s -> !s.startsWith("Awesome"));
    }
}

And in the next example we write assertions to check if a String value ends with an expected value:

package mrhaki;

import org.junit.jupiter.api.Test;
import java.util.regex.Pattern;

import static org.assertj.core.api.Assertions.assertThat;

class StringEndsWith {

    @Test
    void testStringEndsWith() {
        assertThat("Awesome AssertJ").endsWith("AssertJ");

        // We can also check ignoring the casing of the String value.
        assertThat("Awesome AssertJ").endsWithIgnoringCase("assertJ");
    }

    @Test
    void testStringDoesNotEndWith() {
        // Or we we check our String values doesn't end with an expected value.
        assertThat("Awesome Asciidoc").doesNotEndWith("AssertJ");

        // We can also check ignoring the casing of the String value.
        assertThat("Awesome Asciidoc").doesNotEndWithIgnoringCase("assertJ");
    }

    @Test
    void testStringEndsWithRegex() {
        // We can use regular expressions as well, but it is more verbose.
        assertThat("Awesome AssertJ").matches(Pattern.compile(".*AssertJ$"));

        // Also here we can ignore casing.
        assertThat("Awesome AssertJ").matches(Pattern.compile(".*AssertJ$", Pattern.CASE_INSENSITIVE));

        // And check String value doesn't end with expected value.
        assertThat("Amazing Asciidoc").doesNotMatch(Pattern.compile(".*AssertJ$"));
    }

    @Test
    void testStringEndsWithPredicate() {
        // We can use a Predicate as well, but it is more verbose.
        assertThat("Awesome AssertJ").matches(s -> s.endsWith("AssertJ"));

        // If we want to ignore casing we have to first convert our value.
        assertThat("Awesome AssertJ").matches(s -> s.toLowerCase().endsWith("assertj"));

        // And check String value doesn't end with expected value.
        assertThat("Awesome Asciidoc").matches(s -> !s.endsWith("AssertJ"));
    }
}

Written with AssertJ 3.24.2.

June 29, 2023

Awesome AssertJ: Assert toString Method of Object With hasToString Method

AssertJ has many useful methods to write assertions using a fluid API. If we want to test the toString() method implementation of an object we can of course invoke the toString() method inside an assertThat expression and check with the assert method isEqualTo(String) the value. But AssertJ can make that easier so we don’t have to invoke toString() method ourselves. We can use the assert method hasToString(String) on the object we defined in the assertThat expression and specify our expected value as argument to the method. If we want to assert the toString() method doesn’t return an expected value we can use the assert method doesNotHaveToString(String).

In the following example test we use the methods hasToString and doesNotHaveToString for different

package mrhaki;

import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

class HasToString {

    // Helper class used in tests to check toString().
    private static class User {
        private final String username;

        public User(String username) {
            this.username = username;
        }

        public String toString() {
            return String.format("[User{username=%s}]", username);
        }
    }

    @Test
    void testHasToString() {
        // We can assert the toString() method of arbitrary objects
        // with hasToString(String).
        assertThat(new User("mrhaki")).hasToString("[User{username=mrhaki}]");
    }

    @Test
    void testDoesNotHaveString() {
        // If we want to assert the toString() method doesn't return a value
        // we can use doesNotHaveToString(String).
        assertThat(new User("mrhaki")).doesNotHaveToString("User[user=mrhaki]");
    }

    @Test
    void testIntegerToString() {
        // Simple types are automatically boxed to the object wrapper type
        // and then have a toString() method to assert the value for.
        assertThat(42).hasToString("42");  // int value is boxed to Integer.
        assertThat(Integer.valueOf(42)).hasToString("42");
    }

    @Test
    void testListToString() {
        // We can test toString() on List instances.
        var items = List.of("TDD", "JUnit", "AssertJ");
        assertThat(items).hasToString("[TDD, JUnit, AssertJ]");
        assertThat(items).doesNotHaveToString("[TDD,JUnit,AssertJ");
    }

    @Test
    void testMapToString() {
        // We can test toString() on Map instances.
        var map = Map.of("language", "Java");
        assertThat(map).hasToString("{language=Java}");
        assertThat(map).doesNotHaveToString("[language:Java]");
    }
}

Written with AssertJ 3.24.2.

April 21, 2023

Groovy Goodness: Sorting Data With GINQ

GINQ (Groovy-INtegerate Query) is part of Groovy since version 4. With GINQ we can use SQL-like queries to work with in-memory data collections. If we want to sort the data we can use orderby followed by the property of the data we want to sort just like in SQL we can use order by. By default the sort ordering is ascending and null values are put last. We can change the sort ordering by specifying in desc with the orderby clause. Or to make the ascending order explicitly we use the statement in asc. Each of asc and desc also can take an argument to specify how we want null values to be sorted. The default way is to keep null values last in the ordering. If we want to make this explicit we use nullslast as argument to asc or desc. To have null values in the sorted result first we use the argument nullsfirst.

The following example shows all use cases for using orderby when using GINQ:

import groovy.json.JsonSlurper

// Parse sample JSON with a list of users.
def json = new JsonSlurper().parseText('''[
{ "username": "mrhaki", "email": "mrhaki@localhost" },
{ "username": "mrhaki", "email": "user@localhost" },
{ "username": "hubert", "email": "user@localhost" },
{ "username": "hubert", "email": "hubert@localhost" },
{ "username": "hubert", "email": null }
]''')

// Helper method to return a String
// representation of the user row.
def formatUser(row) {
    row.username + "," + row.email
}

// Default ordering is ascending.
// We specify the field name we want to order on.
assert GQ {
    from user in json
    orderby user.username
    select formatUser(user)
}.toList() == [
    'hubert,user@localhost',
    'hubert,hubert@localhost',
    'hubert,null',
    'mrhaki,mrhaki@localhost',
    'mrhaki,user@localhost'
]

// We can explicitly set ordering to ascending.
assert GQ {
    from user in json
    orderby user.email in asc
    select formatUser(user)
}.toList() == [
    'hubert,hubert@localhost',
    'mrhaki,mrhaki@localhost',
    'mrhaki,user@localhost',
    'hubert,user@localhost',
    'hubert,null'
]

// By default null values are last.
// We can also make this explicit as
// option to in asc() or in desc().
assert GQ {
    from user in json
    orderby user.email in asc(nullslast)
    select formatUser(user)
}.toList() == [
    'hubert,hubert@localhost',
    'mrhaki,mrhaki@localhost',
    'mrhaki,user@localhost',
    'hubert,user@localhost',
    'hubert,null'
]

// We can combine multiple properties to sort on.
assert GQ {
    from user in json
    orderby user.username, user.email
    select formatUser(user)
}.toList() == [
    'hubert,hubert@localhost',
    'hubert,user@localhost',
    'hubert,null',
    'mrhaki,mrhaki@localhost',
    'mrhaki,user@localhost'
]

// To order descending we must specify it
// as in desc.
assert GQ {
    from user in json
    orderby user.username in desc
    select formatUser(user)
}.toList() == [
    'mrhaki,mrhaki@localhost',
    'mrhaki,user@localhost',
    'hubert,user@localhost',
    'hubert,hubert@localhost',
    'hubert,null'
]

// We can mix the ordering and set it
// differently for each property.
assert GQ {
    from user in json
    orderby user.username in asc, user.email in desc
    select formatUser(user)
}.toList() == [
    'hubert,user@localhost',
    'hubert,hubert@localhost',
    'hubert,null',
    'mrhaki,user@localhost',
    'mrhaki,mrhaki@localhost'
]

// By default all null values are last,
// but we can use nullsfirst to have null
// values as first value in the ordering.
assert GQ {
    from user in json
    orderby user.username in asc, user.email in desc(nullsfirst)
    select formatUser(user)
}.toList() == [
    'hubert,null',
    'hubert,user@localhost',
    'hubert,hubert@localhost',
    'mrhaki,user@localhost',
    'mrhaki,mrhaki@localhost'
]

Written with Groovy 4.0.11.

Groovy Goodness: Calculate The Median Of A Collection

Since Groovy 4 we can use SQL like queries on in-memory collections with GINQ (Groovy-Integrated Query). GINQ provides some built-in aggregate functions like min, max, sum and others. One of these functions is median. With median we can get the value that is in the middle of the sorted list of values we want to calculate the median for. If the list has an uneven number of elements the element in the middle is returned, but if the list has an even number of elements the average of the two numbers in the middle is returned.

In the following example we see the use of the median function with GINQ:

// List of uneven number of response times.
def responseTimes = [201, 200, 179, 211, 350]

// Get the median from the list of response times.
// As the list has an uneven number of items
// the median is in the middle of the list after
// it has been sorted.
assert GQ {
    from time in responseTimes
    select median(time)
}.toList() == [201]

// List of even number of response times.
responseTimes = [201, 200, 179, 211, 350, 192]

// 2 numbers are the median so the result
// is the average of the 2 numbers.
assert GQ {
    from time in responseTimes
    select median(time)
}.findResult() == 200.5

// Use the GQ annotation and return a List from the method.
@groovy.ginq.transform.GQ(List)
def medianSize(List<String> values) {
    from s in values
    // We can also use an expression to get the median.
    // Here we take the size of the string values to
    // calculage the median.
    select median(s.size())
}

assert medianSize(["Java", "Clojure", "Groovy", "Kotlin", "Scala"]) == [6]

// Sample data structure where each record
// is structured data (map in this case).
// Could also come from JSON for example.
def data = [
    [test: "test1", time: 200],
    [test: "test1", time: 161],
    [test: "test2", time: 427],
    [test: "test2", time: 411],
    [test: "test1", time: 213]
]

// We want to get each record, but also
// the median for all times belonging to a single test.
// We can use the windowing functions provided by GINQ
// together with median.
def query = GQ {
    from result in data
    orderby result.test
    select result.test as test_name,
           result.time as response_time,
           (median(result.time) over(partitionby result.test)) as median_per_test
}

assert query
        .collect { row -> [name: row.test_name,
                           response: row.response_time,
                           median: row.median_per_test] } ==
[
    [name: "test1", response: 200, median: 200],
    [name: "test1", response: 161, median: 200],
    [name: "test1", response: 213, median: 200],
    [name: "test2", response: 427, median: 419],
    [name: "test2", response: 411, median: 419]
]

Written with Groovy 4.0.11.