Skip to content

Automatic hamcrest matcher for model classes

License

Notifications You must be signed in to change notification settings

itsallcode/hamcrest-auto-matcher

Repository files navigation

hamcrest-auto-matcher

Automatic hamcrest matcher for model classes for Java 11

Build Quality Gate Status Coverage Maven Central

Why use hamcrest-auto-matcher?

Writing a Hamcrest matcher for your model classes by extending TypeSafeDiagnosingMatcher is a good idea, because it gives you a readable diff of actual and expected property values. But doing it by hand is tedious and hard to get right, especially for classes with many properties:

  • It is easy to make mistakes in the matches() method.
  • It requires lot's of boiler plate code.
  • Good layout of actual and expected property values is hard to get right.
  • Each property occurs in multiple places which violates the DRY principle.

Project Information

  • Changelog
  • Requirements
    • Java 11
    • Hamcrest 3.0

How to use hamcrest-auto-matcher in your project

Setup dependencies

Gradle

repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'org.itsallcode:hamcrest-auto-matcher:0.8.2'
}

Maven

<dependency>
    <groupId>org.itsallcode</groupId>
    <artifactId>hamcrest-auto-matcher</artifactId>
    <version>0.8.2</version>
    <scope>test</scope>
</dependency>

Assume you have two model classes DemoModel and DemoAttribute:

public class DemoModel {
    private final int id;
    private final String name;
    private final DemoAttribute attr;
    private final List<DemoModel> children;
    private final String[] stringArray;
    private final Long longVal;

    public DemoModel(int id, String name, Long longVal, DemoAttribute attr, String[] stringArray,
            List<DemoModel> children) {
        this.id = id;
        this.name = name;
        this.longVal = longVal;
        this.attr = attr;
        this.stringArray = stringArray;
        this.children = children;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public DemoAttribute getAttr() {
        return attr;
    }

    public List<DemoModel> getChildren() {
        return children;
    }

    public String[] getStringArray() {
        return stringArray;
    }

    public Long getLongVal() {
        return longVal;
    }

    @Override
    public String toString() {
        return "DemoModel [id=" + id + ", name=" + name + ", attr=" + attr + ", children=" + children + ", stringArray="
                + Arrays.toString(stringArray) + ", longVal=" + longVal + "]";
    }
}

public class DemoAttribute {
    private final String value;

    public DemoAttribute(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "DemoAttribute [value=" + value + "]";
    }
}

Use AutoMatcher.equalTo() to create a matcher for your expected model instance. This will use reflection to determine expected property values based on getter methods:

import org.itsallcode.matcher.auto.AutoMatcher;

DemoModel expected = ...;
DemoModel actual = ...;
assertThat(actual, AutoMatcher.equalTo(expected));

Example mismatch report:

Expected: {id=<4711>, longVal=null, name="name1", attr=null, stringArray=null, children=null}
     but: {longVal was <42L>}

Property Detection

AutoMatcher creates properties for methods matching the following criteria:

  • Visibility: public
  • Name: starts with get or is
  • Signature: not void and no arguments
  • Not one of the built-in methods getClass(), getProtectionDomain(), getClassLoader(), getURLs()

If AutoMatcher does not work for your model classes, you can still use ConfigurableMatcher and MatcherConfig which allows you to specify properties and custom matchers explicitly but is much easier to use than TypeSafeDiagnosingMatcher.

import org.itsallcode.matcher.config.MatcherConfig;
import org.itsallcode.matcher.config.ConfigurableMatcher;

public class DemoModelMatcher {
    public static Matcher<DemoModel> equalTo(DemoModel expected) {
        final MatcherConfig<DemoModel> config = MatcherConfig.builder(expected)
                .addEqualsProperty("id", DemoModel::getId)
                .addEqualsProperty("longVal", DemoModel::getLongVal)
                .addEqualsProperty("name", DemoModel::getName)
                .addProperty("attr", DemoModel::getAttr, DemoAttributeMatcher::equalTo)
                .addEqualsProperty("stringArray", DemoModel::getStringArray)
                .addIterableProperty("children", DemoModel::getChildren, DemoModelMatcher::equalTo)
                .build();
        return new ConfigurableMatcher<>(config);
    }
}

Also see DemoModelMatcher as an example.

Development

git clone https://github.com/itsallcode/hamcrest-auto-matcher.git
cd hamcrest-auto-matcher
./gradlew check
# Test report: build/reports/tests/index.html

Using eclipse

Import into eclipse using buildship.

Run sonar analysis

./gradlew clean sonar --info -Dsonar.token=[token]

Check for dependency updates

./gradlew dependencyUpdates

Test Coverage

To calculate and view test coverage:

./gradlew check jacocoTestReport
open build/reports/jacoco/test/html/index.html

Publish to Maven Central

Preparations

  1. Checkout the main branch, create a new branch.
  2. Update version number in build.gradle and README.md.
  3. Add changes in new version to CHANGELOG.md.
  4. Commit and push changes.
  5. Create a new pull request, have it reviewed and merged to main.

Perform the Release

  1. Start the release workflow
  • Run command gh workflow run release.yml --repo itsallcode/hamcrest-auto-matcher --ref main
  • or go to GitHub Actions and start the release.yml workflow on branch main.
  1. Update title and description of the newly created GitHub release.
  2. After some time the release will be available at Maven Central.