Custom Matchers in NHamcrest

Table Of Contents

Overview

While using Nhamcrest, there will be many situation you need to write your own matcher (custom matchers). Think about when you want to assert if a string contains only letters and spaces or an integer array contains only number which is divisible by both 5 and 3 as below examples. At this blog, let’s see how to get it done easily.

Install

Firstly create a Console App through Visual Studio and add this to your nuget package manager (your .csproj file) to get Nhamcrest and its adapter for Nunit framework.

<ItemGroup>
    <PackageReference Include="NHamcrest" Version="3.1.0" />
    <PackageReference Include="NHamcrest.NUnit" Version="3.1.0" />
</ItemGroup>

Usage

Let’s see how we get everything done through two examples below:
a) Write a custom matcher to assert an integer array contains only number which is divisble by both 5 and 3:
To write a custom matcher, firstly we need to create a new class called CheckNumber which inhertit from Matcher class

using NHamcrest;
using NHamcrest.Core;

namespace NHamcrestCustomMatcher.CustomMatchers
{
    public class CheckNumber: Matcher<int>
    {

        private CheckNumber() { }

	     public override void DescribeTo(IDescription description)
        {
            description.AppendText($"should be divisible by both 5 and 3");
        }

        public override void DescribeMismatch(int item, IDescription mismatchDescription)
        {
            mismatchDescription.AppendText("was ").AppendValue(item);
        }

        public override bool Matches(int item)
        {
            return item % 5 == 0 && item % 3 == 0;
        }

        public static Matcher<int> DivisibleByBothFiveAndThree()
        {
            return new CheckNumber();
        }
    }
}

Let’s break down everything:

public class CheckNumber: Matcher<int>

=> Our new class must extend Matcher class with appropriate generic type (int) because all item in array we want to assert is integer type. This generic type can not be any another type.

public override void DescribeTo(IDescription description)
{
   description.AppendText($"should be divisible by both 5 and 3");
}

=> This method overrides the virtual method defined inside Matcher class. It describes why matcher item does not match. Anyways, it will generate the “expected” statement of message when matcher fails like “expected every item should be divisible by both 5 and 3”

public override void DescribeMismatch(int item, IDescription mismatchDescription)
{
   mismatchDescription.AppendText("was ").AppendValue(item);
}

=> This method also overrides the virtual method defined inside Matcher class. It describes why matcher item does not match. Anyways, it will generate the “but” statement of error message when matcher fails like ““but was xyz”

public override bool Matches(int item)
{
   return item % 5 == 0 && item % 3 == 0;
}

=> This override method must return a boolean value that contains our condition for custom matcher.

public static Matcher<int> DivisibleByBothFiveAndThree()
{
   return new CheckNumber();
}

=> This static method will be used in Assert statement afterward. It returns an instance of our defined class.

After complete the definition steps for custom matchers, now is the time to use it. Just need to call it like below. The Every.Item() method is the built-int method of NHamcrest which will loop through all items of the collection and verify all items must satisfy the matcher condition

using NHamcrest;
using NHamcrest.NUnit;
using NHamcrestCustomMatcher.CustomMatchers;

int[] groupA = new int[] { 15, 60, 90, 300 };

AssertEx.That(groupA, Every.Item(CheckNumber.DivisibleByBothFiveAndThree()));

int[] groupB = new int[] { 15, 60, 91, 300 };

AssertEx.That(groupB, Every.Item(CheckNumber.DivisibleByBothFiveAndThree()));

b) Write a custom matcher to assert if a string only contains letters or spaces:
Same as above. Let’s define another custom matcher like this:

using NHamcrest;
using NHamcrest.Core;
using System.Text.RegularExpressions;

namespace NHamcrestCustomMatcher.CustomMatchers
{
    public class CheckString: Matcher<string>
    {
        private CheckString(){}

        public override void DescribeTo(IDescription description)
        {
            description.AppendText($"must only contain letters");
        }

        public override void DescribeMismatch(string item, IDescription mismatchDescription)
        {
            mismatchDescription.AppendText("was ").AppendValue(item);
        }

        public override bool Matches(string item)
        {
            return Regex.IsMatch(item, @"^[a-zA-Z ]+$");
        }

        public static Matcher<string> ContainsOnlyLetters()
        {
            return new CheckString();
        }
    }
}

=> Just need to modify three things:
+ First is the generic type: Matcher<string> because now we are verify string
+ Second is the message in DescribeTo method
+ Three is the logic in Matches method. I use regex to verify if a string contains only letters or spaces.

After complete, we just need to use it:

using NHamcrest;
using NHamcrest.NUnit;
using NHamcrestCustomMatcher.CustomMatchers;

string strA = "I am nineteen years old";

AssertEx.That(strA, CheckString.ContainsOnlyLetters());

string strB = "I am 19 years old";

AssertEx.That(strB, CheckString.ContainsOnlyLetters());

Conclusion

There is a very little document or instruction for writing custom matchers using Nhamcrest. But it is not very complicated when you fully understand. I hope you will find this blog useful. Thanks for reading!

Ask me anything