[JAVA] JUnit assertions and Matcher API

Overview

In JUnit, it is an element responsible for value verification Understand "assertions" and "Matcher API".

What is an assertion?

Assertion literally means affirmation or assertion.

JUnit can be roughly divided

  1. Definition of prerequisites and expected results
  2. Execution of the test target
  3. Comparison verification of expected result and execution result

Unit test is performed in the above three steps. What is an assertion in JUnit? This is the third mechanism for performing "comparison verification between assumed results and execution results".

In the actual code, use it as follows.

Assertion example.java


int expected = 1;
int actual = calc();
assertThat(actual, is(expected));

By writing like this, the test Assert that actual is expected. (Assert that actual is expected.) Can be written in natural language (spoken English).

What is Matcher API?

The method ```is (expected) `` `that appeared in the source code mentioned earlier is It is a static method implemented in the CoreMatcher class of Matcher API. The Matcher API is for assertions that provide a ** value comparison method **. It is a mechanism to provide ** value verification method **, Previously it was implemented only in the JUnit extension library Hamcrest, It is now built into JUnit itself.

Matcher provided by Core Matchers

is "actual is expected."

Verify that the values are equivalent.

assertThat(actual, is(expected));

not "actual is not expected."

Invert the verification result returned by Matcher. It can also be applied to Matchers other than is.

assertThat(actual, is(not(expected)));

nullValue "actual is null."

Since the is method has a type parameter, you cannot pass a null value. Therefore, if you want to perform null verification, use the nullValue method.

assertThat(actual, is(nullValue()));
/* 
The following writing style is also possible,
It is customary to write the above in order to write in a way that is closer to natural language.
*/
assertThat(actual, nullValue());

notNullValue "actual is not null."

Verify that the value is not null.

assertThat(actual, is(notNullValue()));
//Synonymous with
assertThat(actual, is(not(nullValue()));

sameInstance "actual and expected are the same instance."

Verify that the values are equivalent. Whereas the is method compares by equals, sameInstance makes a comparison by `` = relatively.

assertThat(actual, is(sameInstance(expected)));

instanceOf "actual is a value compatible with expected."

Verify that the value inherits the expected value.

assertThat(actual, is(instanceOf(Serializable.class)));

Matcher provided by JUnit Matchers

hasItem "actual contains expected."

For measured values that are iterative (have an iterator) like a collection class Verify that the expected value is included.

List<String> actual = getList();
assertThat(actual, hasItem(expected));

hasItems "actual contains expected."

The content to be verified is the same as hasItem, except that it takes a variable length argument to the assumed value. A useful Matcher to verify that all expected values are included, regardless of order.

List<String> actual = getList();
assertThat(actual, hasItem(expected1, expected2));

Supplement: Matcher provided by hamcrest-library

Another extension library called hamcrest-library It provides a Matcher that can be used for general-purpose verification of collections, numbers, texts, etc. There are various other extension libraries, so before introducing the custom Matcher introduced below, It's a good idea to look for a Matcher that is already available.

Custom Matcher

Matcher can create a custom Matcher that does its own validation. Here, we will implement Matcher that meets the following requirements.

-When comparing and verifying the Date class, compare up to year, month, and day. -Output the assumed value and the measured value in the following format Format: is "Assumed value (yyyy / mm / dd)" but actual is "Actual value (yyyy / mm / dd)"

The following is the implementation of Matcher that meets the above requirements and its call processing.

DateTest.java


import static jp.sample.matcher.IsDate.*;
~abridgement~
        assertThat(new Date(), is(dateOf(2020, 4, 12)));

IsDate.java


// 1.Declaration of Matcher class
public class IsDate extends BaseMatcher<Date> {

    // 3.Implementation of a mechanism to hold assumed values
    private final int yyyy;
    private final int mm;
    private final int dd;
    Object actual;

    // 3.Implementation of a mechanism to hold assumed values
    IsDate(int yyyy, int mm, int dd) {
        this.yyyy = yyyy;
        this.mm = mm;
        this.dd = dd;
    }

    // 4.Implementation of verification process
    @Override
    public boolean matches(Object actual) {
        this.actual = actual;
        if (!(actual instanceof Date)) {
            return false;
        }
        Calendar cal = Calendar.getInstance();
        cal.setTime((Date) actual);
        if (yyyy != cal.get(Calendar.YEAR)) {
            return false;
        }
        if (mm != cal.get(Calendar.MONTH)) {
            return false;
        }
        if (dd != cal.get(Calendar.DATE)) {
            return false;
        }
        return true;
    }

    // 5.Implementation of error messages
    @Override
    public void describeTo(Description desc) {
        desc.appendValue(String.format("%d/%02d/%02d", yyyy, mm, dd));
        if (actual != null) {
            desc.appendText(" but actual is ");
            desc.appendValue(
                new SimpleDateFormat("yyyy/MM/dd").format((Date) actual));
        }
    }

    // 2.Implementation of validation method
    public static Matcher<Date> dateOf(int yyyy, int mm, int dd) {
        return new IsDate(yyyy, mm, dd);
    }
}

1. Declaration of Matcher class

The org.hamcrest.Matcher interface as an implementation class for custom Matcher Implement, but direct implementation of the Matcher interface is deprecated. So we are implementing the Matcher interface Implement by inheriting org.hamcrest.BaseMatcher.

BaseMatcher has the measured value as a type parameter.

/* 
public class [Arbitrary class name] extends BaseMatcher<[Measured value type]> {
}
*/
public class IsDate extends BaseMatcher<Date> {
}

2. Implementation of validation method

Implement the method to be passed to the second argument of assertThat method. Since the assumed value is passed to the second argument, define the constructor of Matcher class Initialize the Matcher class with the expected values. For the mechanism for holding the assumed value, refer to "3. Implementation of the mechanism for holding the assumed value".

/*
    public static Matcher<[Measured value type]> [Arbitrary method name]([Valuerequiredforinitialization]) {
        return [Matcher class constructor];
    }
*/
    public static Matcher<Date> dateOf(int yyyy, int mm, int dd) {
        return new IsDate(yyyy, mm, dd);
    }

3. Implementation of a mechanism to hold the assumed value

Actual measurement values and assumed values are required to verify the values. Therefore, the Matcher class implements a mechanism to hold them as members.

    //Member holding the expected value
    private final int yyyy;
    private final int mm;
    private final int dd;
    //Member holding the measured value
    Object actual;

    //Constructor to set the expected value
    IsDate(int yyyy, int mm, int dd) {
        this.yyyy = yyyy;
        this.mm = mm;
        this.dd = dd;
    }

4. Implementation of verification process

When the assertThat method is executed, in the process The matches method defined in the Matcher interface is called. If the return value is false in the matches method, the test fails, and if it is true, the test is successful. Since the measured value is taken as an argument, it is verified with the assumed value held by the Matcher class. Implement so that true is returned if the verification result is as expected, and false if it is different.

    @Override
    public boolean matches(Object actual) {
        //Hold the passed measured value as a member of the Matcher class
        this.actual = actual;
        //Type check(Write promised)
        if (!(actual instanceof Date)) {
            return false;
        }
        //The following is an original implementation, so implement it according to the purpose
        Calendar cal = Calendar.getInstance();
        cal.setTime((Date) actual);
        if (yyyy != cal.get(Calendar.YEAR)) {
            return false;
        }
        if (mm != cal.get(Calendar.MONTH)) {
            return false;
        }
        if (dd != cal.get(Calendar.DATE)) {
            return false;
        }
        return true;
    }

5. Implementation of error message

Like the matches method, the method defined in the Matcher interface. It is called only when the test result is unsuccessful, and the error message is defined here.

    @Override
    public void describeTo(Description desc) {
        //Expected value output
        desc.appendValue(String.format("%d/%02d/%02d", yyyy, mm, dd));
        //Output of measured value
        if (actual != null) {
            desc.appendText(" but actual is ");
            desc.appendValue(
                new SimpleDateFormat("yyyy/MM/dd").format((Date) actual));
        }
    }

Output result to console


java.lang.AssertionError:
Expected: is "2011/02/10" but actual is "2012/03/08"
     got: <Thu Mar 08 23:02:49 JST 2012>

Error message template
[Error class name]
Expected: is [Message defined by describeTo method]
     got: [Measured value toString output result]

appendValue method

//When a character string is passed as an argument
"2012/03/08" (Value enclosed in double quotes)
//When an object is passed as an argument
<Thu Mar 08 23:02:49 JST 2012> (ToString output result of the passed object)

appendText method

 but actual is  (Not surrounded by double quotes)

References

This article was written with reference to the following information.

-[Introduction to JUnit Practice ── Systematic Unit Testing Techniques](https://www.amazon.co.jp/JUnit%E5%AE%9F%E8%B7%B5%E5%85%A5%E9 % 96% 80-% E4% BD% 93% E7% B3% BB% E7% 9A% 84% E3% 81% AB% E5% AD% A6% E3% 81% B6% E3% 83% A6% E3% 83% 8B% E3% 83% 83% E3% 83% 88% E3% 83% 86% E3% 82% B9% E3% 83% 88% E3% 81% AE% E6% 8A% 80% E6% B3% 95-WEB-PRESS-plus-ebook / dp / B07JL78S95)

Recommended Posts

JUnit assertions and Matcher API
Test Web API with junit