Although JUnit 5 offers a large variety of Assertions, sometimes we need some additional ones that we don't have in JUnit 5.
For that, we can use some external assertion libraries and one of these that I will go over in this tutorial is Hamcrest.
In order to use Hamcrest, we first need to import it into the project, and then we need to create the Assertions which are available for asserting either Objects or Strings or even collections.
Now, in order to start using Hamcrest, we first need to go to our pom.xml
file.
In the dependencies section, we need to add the Hamcrest dependency.
This one will be the one you see right here.
We have the groupId
org.hamcrest
, and the artifactId
hamcrest
.
The latest version
available is 2.2
, at this time.
If you want to look for newer versions, you will need to go to https://mvnrepository.com/ and search for this artifactId
there.
Now, in order to have the Hamcrest dependency available, we will just go to the "Maven" pane and we will do a reload of the project.
On the left-hand side, in the "External Libraries" section, we will have to wait for the Hamcrest entry to be present, which has happened now.
I will go back to the AssertionsTest
and we will continue with writing tests here.
One of the available options in Hamcrest is to do Assertions on collections, which means we can do Assertions for Lists or for Maps, or even for Arrays, if we want to.
I will start with an example for Maps and I will create a new test.
Then, I will create a new Map inside it that we will use for the Assertion.
I will call this assertForMapTest
, and the first thing will be to create a Map and I'll create a Map whose keys will be of String type and values will be Integers.
I will name this one theMap
- again, naming is everything so in your tests, do use a relevant name for it.
Then I will say new HashMap<>()
and I will add three values here.
I will say theMap.put("firstKey",1)
, where the value corresponding to this key will be 1.
Then we will say theMap.put("secondKey", 2)
, and similarly, let's go with a theMap.put("thirdKey", 3)
.
So right now, we have the Map available.
In order to create the Assertion in Hamcrest, we usually start with assertThat()
.
Of course we need to import this from org.hamcrest.MatcherAssert.assertThat
.
Here, we can have two parameters.
The first item will always be the subject of the assertion, so what we are trying to compare to something or what we want to check some properties about.
In our case, we want to check that theMap
has a key, which we will provide as a string value here.
Let's say that we want to check that the key secondKey
is part of the map.
For that, we will say Matchers.hasKey()
and here we will just provide the string that we want to check for - "secondKey"
, in our case.
@Test
void assertForMapTest() {
Map<String, Integer> theMap = new HashMap<>();
theMap.put("firstKey", 1);
theMap.put("secondKey", 2);
theMap.put("thirdKey", 3);
assertThat(theMap, Matchers.hasKey("secondKey"));
}
I will run the test right now and we will see that this test passes.
If we change the string "secondKey"
to something that is not part of the Map - for example, "secondKey1"
and we rerun the test, we will see a message that this key is not part of the map.
So the map is actually listed here and as you can see, none of the keys are secondKey1
, so this is what a failed assertion looks like.
If we want, for example, to check that a value is part of the map, we could do that by saying again, assertThat()
, and again, we are referring to theMap
and again, we will say Matchers.hasValue(2)
, and here we specify that we need hasValue()
as the method to be used and for the value, we will check if the value 2
is in theMap
.
If we run these, the test will fail because the second assertion fails, but the first one will pass successfully.
The message we get here is still the one regarding the key.
However, if we change the value we're interested in to, let's say, 22
, of course, that assertion will fail.
Now, when we run the test, we will see that 22
again is not part of the map.
So 22
is not a valid value when it comes to the map, and this is how we can check some properties of a map - so using the assertThat()
method and the Matchers.hasValue()
and Matchers.hasKey()
methods.
Now, let's say we are interested in checking whether a value is part of a List.
I will create an assertForList
test here, and I will create a new List of Strings.
I will just call it theList
, and it will be an Arrays.asList()
.
Here, again, I will provide some values and I will say "firstString", "secondString", "thirdString"
.
Similar to what we had in the previous test, the assertions will be nothing more than assertThat()
, and then we will need to specify who we are interested in.
In this case, it will be theList
, and we want to see whether Matches.hasItem("thirdString")
, which is the value that we expect to see in our List.
@Test
void assertForList() {
List<String> theList = Arrays.asList("firstString", "secondString",
"thirdString");
assertThat(theList, Matchers.hasItem("thirdStrings"));
}
When we will run this test, it will pass, because of course, this item is part of the List.
However, if we changed it to "thirdStrings"
, the test will now fail when we run it again, because this is not a valid item.
It is not part of the List, of course.
The list, as you can see here, has these values, and the one we provided is not part of the list, so of course the Assertion will fail.
Hamcrest has a lot of options available.
There are specific methods for specific types of data.
One interesting feature, for example, is the use of "all of" or "any of", so in this case, you can provide multiple matchers to be asserted against the same object or list or string or whatever you have.
You can assert either all of those conditions - so you want all of them to be true - or you can assert that at least one of them is true.
We can see how that works by just copy-pasting these tests again.
So we have theList
, and we will look at the option anyOf
.
In this case, instead of only one Matcher, we can define several ones to be evaluated, but we are going to say Matchers.anyOf()
, so now we want to provide several conditions and we're interested if any of them actually happen, not all of them.
If any of them fails, it doesn't matter, as long as there is at least one which matches our condition, then this assertion will pass.
Let's say that we want these assertions to be Matchers.hasItem()
and let's say, we're interested in the "thirdString"
to be a part of this list, but we're also interested whether this list has the item, "noString"
.
In this case, of course, the "thirdString" is part of the list, and the "noString" is not.
@Test
void assertForAnyOf() {
List<String> theList = Arrays.asList("firstString", "secondString",
"thirdString");
assertThat(theList, Matchers.anyOf(hasItem("thirdString"),
Matchers.hasItem("noString")));
}
However, because we're seeing Matchers.anyOf()
, this test will pass.
So anyOf
means "or" - either the first element is in the list, or the second element is in the list.
If instead we would use allOf
here, this would only pass if both the Matchers within the allOf
would evaluate to true.
So, if we were to run this right now, this will fail because "noString"
is not part of the List of strings.
Keep this in mind - if you need to perform multiple assertions on the same item, this is a good way of doing that - using anyOf
or allOf
.
Another example we can look at is to check whether a List contains some items in any order.
I will create a new test here - I will copy-paste the previous one and I will call it assertForContainsAnyOrder
.
Here, we already have theList
created and I'm just going to say assertThat(theList, Matchers.containsInAnyOrder()
, and here I will just pass the same values.
We will run the test to see that it passes, after which I will switch the order of these values.
Running this test will be successful now.
Let's say that we move the "thirdString" to actually be the second string in the List.
@Test
void assertForContainsAnyOrder() {
List<String> theList = Arrays.asList("firstString", "secondString",
"thirdString");
assertThat(theList, Matchers.containsInAnyOrder("firstString",
"thirdString", "secondString"));
}
If we run the test again, this will again pass because we specified some strings that are part of the list, even though they are not in exactly this order.
In case we want to check for multiple values belonging to a List no matter the order, we can use the containsInAnyOrder
matcher.
I will not go over all of the Matchers that we have in Hamcrest because there are quite a lot.
I will just briefly let you know that, for example, there are some "equals" assertions.
For example, we can compare two objects or two values, or we can even check if a value is greaterThan
another value or lessThan
another value.
We can also compare some strings - for example, we can check whether we have emptyString
- we can check whether a string is empty or null
and so on.
If you're interested in working with Hamcrest, they do have a lot of Assertions here and you just need to take a look at the documentation to see what are all the available Assertions that you can use in your test.
One last thing before we close up the chapter is to do a little bit of cleanup.
I will use Alt+Enter
to add the static imports for all of the Matchers that I've used, just so that we have less code.
So static imports means we have a shorter assertion because we're not specifying Matchers.
anymore.
The test is going to run exactly the same. It's just that we are doing the cleanups for the Matchers here and we are done.