AssertThat method and use of Matcher

Asked

Viewed 2,219 times

2

To perform unit tests using the method assertThat in Junit, you need to pass an object Matcher as a parameter.

public void assertThat(Object o, Matcher matcher){
        ...
}

An example of using passing an expression:

@Test
public void testWithMatchers() {
    assertThat("1991", is("2019"));
}

In that regard, I would like to know the following:

  • Why it is necessary to use the Matcher?
  • What are the advantages of using it?

1 answer

3


I did the tests below with Java 8, Junit 4.12 and hamcrest-all 1.3


At first it seems to use assertThat would not be necessary, as similar results can be obtained with the other methods assertXXX. Examples:

// são equivalentes
assertThat("1991", is(algumValor)); // sendo que algumValor é uma String
assertEquals("1991", algumValor);

// são equivalentes
assertThat("1991", is(not("2019")));
assertNotEquals("1991", "2019");

But there are some advantages to using assertThat. Suppose I have a list of String and one of the tests checks whether it contains two specific elements:

List<String> list = Arrays.asList("abc", "def", "ghi");
assertTrue(list.contains("abc") && list.contains("xyz"));

Although it is not such a complicated code to understand, the version with assertThat and the use of matcher org.hamcrest.core.IsCollectionContaining (that has the method hasItems) leaves the code more readable and expressive (at least for those who know English):

// é quase uma frase em inglês (verifique se a lista tem os itens "abc" e "xyz")
assertThat(list, hasItems("abc", "xyz"));

Another advantage is the error message if the test fails. While assertTrue only gave me one AssertionError (without any additional message, only the line that failed), the assertThat gave this message:

java.lang.AssertionError: 
Expected: (a collection containing "abc" and a collection containing "xyz")
     but: a collection containing "xyz" was "abc", was "def", was "ghi"

Much more detailed, describing exactly the problem (a Collection containing "abc" and "Xyz", but she only had "abc", "def" and "ghi").

Another example:

import static org.hamcrest.core.AllOf.allOf;
import static org.hamcrest.number.OrderingComparison.greaterThan;
import static org.hamcrest.number.OrderingComparison.lessThan;

int x = 5;
assertThat(x, allOf(greaterThan(1), lessThan(3)));

allOf receives a list of matchers and check if all are valid. In case, I used greaterThan and lessThan, that is, I check if the number is greater than 1 and less than 3. How I tested the value 5, the result is a AssertionError with the message:

java.lang.AssertionError: 
Expected: (a value greater than <1> and a value less than <3>)
     but: a value less than <3> <5> was greater than <3>

I could do too:

int x = 5;
assertTrue(x > 1 && x < 3);

But again, the AssertionError does not give me a descriptive message, only the line that gave the error. And the version with assertThat and the matchers makes the code closer to natural language (or more "expressive", "readable", etc.). Of course this depends on the opinion of each one, but I think it is positive that you can choose the option you think best.


Besides being able to be chained and combined, as shown in the examples above, there is still the possibility of create your own matcher, where some more complex and/or specific validation is required, for example.

To documentation suggests that a subclass of org.hamcrest.TypeSafeMatcher, which checks that the value being tested is not null and makes the cast for its type. Example:

import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;

public class MyMatcher extends TypeSafeMatcher<String> {
    @Override
    protected boolean matchesSafely(String item) {
        // aqui pode ter o critério que você quiser
        // retorna true ou false, indicando se a String é válida ou não
    }

    @Override
    public void describeTo(Description description) {
        // coloque uma mensagem bem descritiva aqui
        description.appendText("não passou nos critérios");
    }

    public static MyMatcher criterioXyz() {
        return new MyMatcher();
    }
}

And to use it:

import static br.tests.MyMatcher.criterioXyz;

assertThat("string", criterioXyz());

Obviously criterioXyz() should be replaced by a more significant name indicating what the matcher is checking out.


Of course it is possible to do the same check using the other methods assertXXX. It is up to you to decide whether the extra complexity of the matchers is a fair price to pay to increase the readability of the code, in addition to having more descriptive error messages and the possibility to create your own matchers (this last item I confess I never had to use, but you may need it and think it’s worth using).

Browser other questions tagged

You are not signed in. Login or sign up in order to post.