|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: A Friendlier NUnit PredicateConstraint |
| 4 | +excerpt: I've taken to using NUnit's constraint model for my unit tests, as I find it a great way to write readable tests which state exactly what they prove. NUnit has lots of built-in Constraints, but its PredicateConstraint doesn't return very helpful error messages; here's a friendlier version, which does. |
| 5 | +tags: [C#, Automated Testing] |
| 6 | +--- |
| 7 | + |
| 8 | +After recently reading [a book](https://www.amazon.co.uk/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627) |
| 9 | +on effective unit testing, I've taken to using the |
| 10 | +[NUnit constraint model](https://www.nunit.org/index.php?p=constraintModel&r=2.4) for my unit tests. |
| 11 | +I find it a great way to write readable tests which state exactly what they prove; for example, it's |
| 12 | +pretty clear what this test shows, just from reading the code as a sentence: |
| 13 | + |
| 14 | +```csharp |
| 15 | +Assert.That(AnAccount(WithNoOrders), HasAnOutstandingBalanceOf(Zero)); |
| 16 | +``` |
| 17 | + |
| 18 | +In NUnit's `Constraint` model the first argument to `Assert.That` returns an object to test, and |
| 19 | +the second returns an NUnit `Constraint` which determines if the test object is in an expected state. |
| 20 | +In this example the `AnAccount()` method would return an `Account` object, and the |
| 21 | +`HasAnOutstandingBalanceOf()` method would return an NUnit `Constraint` which tests the |
| 22 | +`Account.OutstandingBalance` property against an expected value. |
| 23 | + |
| 24 | +NUnit has simple `Constraint`s for checking if an object under test is null, an empty collection, etc, |
| 25 | +and for more complex tests it has a |
| 26 | +[`PredicateConstraint`](https://code.google.com/p/nunit4netce-and-silverlight/source/browse/trunk/NUnit-2.5.3.9345/nunit.framework/Constraints/PredicateConstraint.cs?spec=svn2&r=2), |
| 27 | +which takes a `Predicate` to run. The `HasAnOutstandingBalanceOf` method in our example could use |
| 28 | +a `PredicateConstraint` to run its test like this: |
| 29 | + |
| 30 | +```csharp |
| 31 | +private static Constraint HasAnOutstandingBalanceOf( |
| 32 | + int expectedOutstandingBalance) |
| 33 | +{ |
| 34 | + return new PredicateConstraint<Account>( |
| 35 | + a => a.OutstandingBalance == expectedOutstandingBalance); |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +When the test runs, the test runner will call the supplied `Predicate` to check if the test has |
| 40 | +passed. The thing is, if the test fails, the NUnit output will say something like this: |
| 41 | + |
| 42 | +```csharp |
| 43 | +Expected: <a value matching lambda> |
| 44 | +Was: 1.00 |
| 45 | +``` |
| 46 | + |
| 47 | +Now, **'<a value matching lambda>'** isn't a very helpful message when diagnosing what the |
| 48 | +test was expecting. To get more helpful messages, I've written a `FriendlyPredicateConstraint`; |
| 49 | +it takes a predicate just like the `PredicateConstraint`, but also takes the expected value and |
| 50 | +a Func to retrieve the actual value, so these can be used in the test results if something goes wrong. |
| 51 | + |
| 52 | +Here's the code: |
| 53 | + |
| 54 | +```csharp |
| 55 | +using System; |
| 56 | +using NUnit.Framework.Constraints; |
| 57 | + |
| 58 | +public class FriendlyPredicateConstraint<T> : PredicateConstraint<T> |
| 59 | +{ |
| 60 | + private readonly string _expectedValue; |
| 61 | + private readonly Func<T, object> _actualValueGetter; |
| 62 | + |
| 63 | + public FriendlyPredicateConstraint( |
| 64 | + Predicate<T> matchingPredicate, |
| 65 | + object expectedValue, |
| 66 | + Func<T, object> actualValueGetter) |
| 67 | + : base(matchingPredicate) |
| 68 | + { |
| 69 | + _expectedValue = expectedValue.ToString(); |
| 70 | + _actualValueGetter = actualValueGetter; |
| 71 | + } |
| 72 | + |
| 73 | + public override bool Matches(object actual) |
| 74 | + { |
| 75 | + // This method is called to find out if the object returned |
| 76 | + // by the test satisfies the Constraint: |
| 77 | + bool matches = base.Matches(actual); |
| 78 | + |
| 79 | + if (!matches && (_actualValueGetter != null) && (actual is T)) |
| 80 | + { |
| 81 | + // The test failed, so retrieve the actual value from the |
| 82 | + // object under test. 'this.actual' is a property in the |
| 83 | + // base |
| 84 | + // class which supplies the test output with the actual |
| 85 | + // value: |
| 86 | + this.actual = _actualValueGetter.Invoke((T)actual); |
| 87 | + } |
| 88 | + |
| 89 | + return matches; |
| 90 | + } |
| 91 | + |
| 92 | + public override void WriteDescriptionTo(MessageWriter writer) |
| 93 | + { |
| 94 | + // If the test fails then this method is called to print the |
| 95 | + // expected value to the test output: |
| 96 | + writer.WriteExpectedValue(_expectedValue); |
| 97 | + } |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +So now HasAnOutstandingBalanceOf can be rewritten like this: |
| 102 | + |
| 103 | +```csharp |
| 104 | +private static Constraint HasAnOutstandingBalanceOf( |
| 105 | + int expectedOutstandingBalance) |
| 106 | +{ |
| 107 | + return new FriendlyPredicateConstraint<Account>( |
| 108 | + a => a.OutstandingBalance == expectedOutstandingBalance, |
| 109 | + // The expected value: |
| 110 | + "OutstandingBalance = " + expectedOutstandingBalance, |
| 111 | + // The actual value: |
| 112 | + a => "OutstandingBalance = " + a.OutstandingBalance); |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +...and if the test fails, the output will say this: |
| 117 | + |
| 118 | +```csharp |
| 119 | +Expected: OutstandingBalance = 0.00 |
| 120 | +Was: OutstandingBalance = 1.00 |
| 121 | +``` |
| 122 | + |
| 123 | +Which is much more helpful :) |
0 commit comments