How to perform deeper matching of keys and values with assertj
Asked Answered
L

8

25

Say I have a class like this:

public class Character {
   public Character(String name){
      this.name = name;
   }
   private String name;
   public String getName() { return name; }
}

And later, a Map

Map<Character, Integer> characterAges = new HashMap<Character, Integer>();
characterAges.put(new Character("Frodo"), 34);

Using assertj, what is the best way to test that characterAges includes the "Frodo" character? For the age, I can do:

assertThat(characterAges).hasValue(34);

And I know I could do:

assertThat(characterAges.keySet())
               .extracting("name")
               .contains("Frodo");

But then I lose my fluency. What I really want it something like this:

assertThat(characterAges)
               .hasKey(key.extracting("name").contains("Frodo")
               .hasValue(34);

Or even better, so that I can make sure my key and value match:

assertThat(characterAges)
               .hasEntry(key.extracting("name").contains("Frodo"), 34);

Is something like this possible?

Luann answered 17/6, 2014 at 17:47 Comment(0)
B
6

There is no easy solution for this. One way is to implement a custom Assertion for the character map. Here is a simple custom Assertion example for this problem:

public class CharacterMapAssert extends AbstractMapAssert<MapAssert<Character, Integer>, Map<Character, Integer>, Character, Integer> {

    public CharacterMapAssert(Map<Character, Integer> actual) {
        super(actual, CharacterMapAssert.class);
    }

    public static CharacterMapAssert assertThat(Map<Character, Integer> actual) {
        return new CharacterMapAssert(actual);
    }

    public CharacterMapAssert hasNameWithAge(String name, int age) {
        isNotNull();

        for (Map.Entry<Character, Integer> entrySet : actual.entrySet()) {
            if (entrySet.getKey().getName().contains(name) && (int) entrySet.getValue() == age) {
                return this;
            }
        }

        String msg = String.format("entry with name %s and age %s does not exist", name, age);
        throw new AssertionError(msg);
    }

}

In the test case:

assertThat(characterAges).hasNameWithAge("Frodo", 34);

Be aware that you have for every custom data structure to write your own assertion. For you Character class you can generate a assertion with the AssertJ assertions generator.


Update Java 8

With Java 8 can also the Lambda Expression used

    assertThat(characterAges).matches(
            (Map<Character, Integer> t)
            -> t.entrySet().stream().anyMatch((Map.Entry<Character, Integer> t1)
                    -> "Frodo".equals(t1.getKey().getName()) && 34 == t1.getValue()),
            "is Frodo and 34 years old"
    );
Barbette answered 1/2, 2015 at 10:24 Comment(2)
Since 3.6.0 there is a method "hasEntrySatisfying()" that solves this exactly. See joel-costigliola.github.io/assertj/…Luann
I think that it should be "extends AbstractMapAssert<CharacterMapAssert, Map<Character, Integer>, Character, Integer>". Otherwise you won't be able to use you custom method after the standard one, e.g. "assertThat(characterAges).containsEntry("a", "b").hasNameWithAge("Frodo", 34);"Posehn
F
7

You can also do something like this:

assertThat(characterAges).contains(entry("Frodo", 34), ...);

See https://github.com/joel-costigliola/assertj-core/wiki/New-and-noteworthy#new-map-assertions

Frankpledge answered 1/10, 2015 at 15:6 Comment(1)
That seems close, but in my question the key is a Character, not a String. How do I assert that it is a Character with a name of "Frodo"?Luann
B
6

There is no easy solution for this. One way is to implement a custom Assertion for the character map. Here is a simple custom Assertion example for this problem:

public class CharacterMapAssert extends AbstractMapAssert<MapAssert<Character, Integer>, Map<Character, Integer>, Character, Integer> {

    public CharacterMapAssert(Map<Character, Integer> actual) {
        super(actual, CharacterMapAssert.class);
    }

    public static CharacterMapAssert assertThat(Map<Character, Integer> actual) {
        return new CharacterMapAssert(actual);
    }

    public CharacterMapAssert hasNameWithAge(String name, int age) {
        isNotNull();

        for (Map.Entry<Character, Integer> entrySet : actual.entrySet()) {
            if (entrySet.getKey().getName().contains(name) && (int) entrySet.getValue() == age) {
                return this;
            }
        }

        String msg = String.format("entry with name %s and age %s does not exist", name, age);
        throw new AssertionError(msg);
    }

}

In the test case:

assertThat(characterAges).hasNameWithAge("Frodo", 34);

Be aware that you have for every custom data structure to write your own assertion. For you Character class you can generate a assertion with the AssertJ assertions generator.


Update Java 8

With Java 8 can also the Lambda Expression used

    assertThat(characterAges).matches(
            (Map<Character, Integer> t)
            -> t.entrySet().stream().anyMatch((Map.Entry<Character, Integer> t1)
                    -> "Frodo".equals(t1.getKey().getName()) && 34 == t1.getValue()),
            "is Frodo and 34 years old"
    );
Barbette answered 1/2, 2015 at 10:24 Comment(2)
Since 3.6.0 there is a method "hasEntrySatisfying()" that solves this exactly. See joel-costigliola.github.io/assertj/…Luann
I think that it should be "extends AbstractMapAssert<CharacterMapAssert, Map<Character, Integer>, Character, Integer>". Otherwise you won't be able to use you custom method after the standard one, e.g. "assertThat(characterAges).containsEntry("a", "b").hasNameWithAge("Frodo", 34);"Posehn
H
6

The following methods of AbstractMapAssert will work for you:

  1. containsExactlyEntriesOf Verifies that the actual map contains only the entries of the given map and nothing else, in order. This should be used with TreeMap. HashMap will not guarantee order and your test will randomly fail.

  2. containsExactlyInAnyOrderEntriesOf Verifies that the actual map contains only the given entries and nothing else, in any order. This will work with HashMap.

import java.util.Map;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

public class TestMapAssertions {

    @Test
    public void testCreateMap() {
        //Call
        Map<String, String> actual = ClassUnderTest.createMap();

        //Assert
        Assertions.assertThat(actual).containsExactlyInAnyOrderEntriesOf(
                Map.of("Key1", "Value1", "Key2", "Value2")
        );
    }

    public static class ClassUnderTest {

        public static Map<String, String> createMap() {
            return Map.of("Key1", "Value1", "Key2", "Value2");
        }
    }
}
Howze answered 22/1, 2021 at 21:42 Comment(0)
I
5

Since 3.6.0, you can use hasEntrySatisfying:

 assertThat(characterAges)
            .hasSize(1)
            .hasEntrySatisfying(aKey, e ->
                    assertThat(e)
                            .isEqualTo(99.99)
            );

In your case, if you cannot use the key for lookup you can use Condition-based hasEntrySatisfying (more verbose).

Indefinable answered 24/6, 2021 at 6:47 Comment(0)
A
2

I am not sure about the date/version of introduction, but there are a bunch of assertions in MapAssert now. From http://joel-costigliola.github.io/assertj/core-8/api/org/assertj/core/api/MapAssert.html:

contains(Map.Entry... entries) - Verifies that the actual map contains the given entries, in any order.

containsAnyOf(Map.Entry... entries) - Verifies that the actual map contains at least one of the given entries.

containsExactly(Map.Entry... entries) - Verifies that the actual map contains only the given entries and nothing else, in order.

containsKeys(KEY... keys) - Verifies that the actual map contains the given keys.

containsOnly(Map.Entry... entries) - Verifies that the actual map contains only the given entries and nothing else, in any order.

containsOnlyKeys(KEY... keys) - Verifies that the actual map contains only the given keys and nothing else, in any order.

containsValues(VALUE... values) - Verifies that the actual map contains the given values.

doesNotContain(Map.Entry... entries) - Verifies that the actual map does not contain the given entries.

doesNotContainKeys(KEY... keys) - Verifies that the actual map does not contain any of the given keys.

extracting(Function,Object>... extractors) - Uses the given Functions to extract the values from the object under test into a list, this new list becoming the object under test.

For your example, containsExactly() should do the trick.

Attention answered 21/11, 2018 at 17:1 Comment(0)
E
1

What about using .entrySet() with .extracting()?

assertThat(characterAges.entrySet())
 .extracting(
       entry -> entry.getKey().getName(), 
       Map.Entry::getValue)
 .contains(tuple("Frodo", 34));

Escallop answered 30/4, 2019 at 13:6 Comment(0)
C
0

If you didn't want to go down the custom assert route, and could access the instances of the characters in the SUT (system under test) in the test, another option could be

In the SUT:

Character frodo = new Character("Frodo");
characterAges.put(frodo, 34);

And in the test

MapEntry frodoAge34 = MapEntry.entry(frodo, 34);
assertThat(characterAges).contains(frodoAge34);
Cp answered 14/9, 2018 at 10:25 Comment(0)
O
0

Below is my example for asserting the nested map of response from actuator endpoint.

Below is the sample response from actuator endpoint. Response is a json response with two root level keys named "git" and "build" both of them are nested json. Below is an example of testing the build json structure

{
"git": {
    "branch": "my-test-branch",
    "build": {
        "time": "2022-03-08T12:43:00Z",
        "version": "0.0.1-SNAPSHOT",
        "user": {
            "name": "gituser",
            "email": "[email protected]"
        },
        "host": "hostName"
    }
},
"build": {
    "artifact": "test-project",
    "name": "test-name",
    "time": "2022-03-08T12:45:07.389Z",
    "version": "0.0.1-SNAPSHOT",
    "group": "com.exmaple"
}

}

Below is the test.

    @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
    class MyApplicationTest {
      
      @Autowired
      private TestRestTemplate restTemplate;
    
      @Test
  void testInfoActuatorEndpoint() {
    Map responseMap = this.restTemplate.getForObject("/actuator/info", Map.class);
    assertThat(responseMap)
            .hasSize(2)
            .containsKey("git")
            .containsKey("build")
            .extracting("build")
            .hasFieldOrPropertyWithValue("artifact", "test-project")
            .hasFieldOrPropertyWithValue("name", "test-name")
            .hasFieldOrPropertyWithValue("version", "0.0.1-SNAPSHOT")
            .hasFieldOrPropertyWithValue("group", "com.example");
    }
}
Outworn answered 8/3, 2022 at 12:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.