23

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?

Daniel Käfer
  • 4,458
  • 3
  • 32
  • 41
pduncan
  • 1,330
  • 2
  • 15
  • 26

8 Answers8

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"
    );
Bombe
  • 81,643
  • 20
  • 123
  • 127
Daniel Käfer
  • 4,458
  • 3
  • 32
  • 41
  • Since 3.6.0 there is a method "hasEntrySatisfying()" that solves this exactly. See https://joel-costigliola.github.io/assertj/assertj-core-news.html#assertj-core-3.6.0-hasEntrySatisfying – pduncan May 07 '19 at 19:23
  • 1
    I think that it should be "extends AbstractMapAssert, 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);" – Anton Shelenkov Jul 01 '21 at 15:27
5

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

clint
  • 14,402
  • 12
  • 70
  • 79
  • 6
    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"? – pduncan Oct 01 '15 at 19:43
5

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");
        }
    }
}
WorkAlcoholic
  • 51
  • 1
  • 2
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).

laffuste
  • 16,287
  • 8
  • 84
  • 91
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.

chelmertz
  • 20,399
  • 5
  • 40
  • 46
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);
Nelson Wright
  • 506
  • 9
  • 18
0

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

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

artvolk
  • 9,448
  • 11
  • 56
  • 85
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": "gituseremail@domain.com"
        },
        "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");
    }
}
Sanjay Bharwani
  • 3,317
  • 34
  • 31