Skip to content

Acceptance criteria

rklec edited this page Aug 25, 2021 · 2 revisions

The Acceptance Criteria is an API used for defining the criteria that a mapping must satisfy for a convention to be applied to it.

The criteria is defined inside the Accept method of a convention that implements an acceptance interface. The API is based around the IAcceptanceCriteria interface, which exposes various methods that record the criteria against the TInspector instance.

Inspectors are a view onto your mapped entities and their parts, they represent a read-only version and are just for ''inspecting'' the existing structure and values.

A typical example of a convention that's defined with a acceptance criteria would look like this:

public class LegacyEntityTableConvention : IClassConvention, IClassConventionAcceptance
{
  public void Accept(IAcceptanceCriteria<IClassInspector> criteria)
  {
    criteria.Expect(x => x.EntityType.IsAny(typeof(OldClass), typeof(AnotherOldClass)));
  }

  public void Apply(IClassInstance instance)
  {
    instance.Table("tbl_" + instance.EntityType.Name);
  }
}

Expectations

Expectations are what the Acceptance Criteria is all about. Expectations are recorded in the Accept definition then played back over your mappings; any mappings that satisfy the expectations will have the conventions applied to them.

An expectation is defined using the Expect method of the criteria instance. This method has two definitions, one that takes a Func<TInspector, bool>, and one that takes a lambda and an acceptance criterion instance.

criteria.Expect(x => x.EntityType == typeof(Example));

That's the Func<TInspector, bool> signature, where the body of the delegate (x.EntityType == typeof(Example)) can be substituted for any code as long as it returns a boolean. There are several extension methods available to help keep things clean when using this syntax, which you can read about #Extension methods. In this example we've set an expectation that the EntityType property of the mapping will be equal to the Example type.

criteria.Expect(x => x.TableName, Is.Not.Set);

That's the other signature, which takes a property lambda and a criterion instance. You can read more about criterions below, but for now all you need to know is they look at the property you specify and determine if it fits a certain criteria. In this case we're expecting that the TableName property hasn't been set.

Chaining expectations

Expectations are cumulative, you can specify multiples and they will work together. Taking the examples we used above, we could chain them together for a convention that should only be applied to Example types without a table name.

criteria
  .Expect(x => x.EntityType == typeof(Example))
  .Expect(x => x.TableName, Is.Not.Set);

You can chain as many expectations together as needed.

As the default behaviour of the Acceptance Criteria is effectively to and everything, there's separate methods available to simulate or's.

Firstly there's Either, which takes two arguments, both of which are sub-acceptance criteria's. If the mapping satisfies either of the criterias, then the convention will be applied.

criteria.Either(
  sub =>
    sub.Expect(x => x.EntityType == typeof(Example)),
  sub =>
    sub.Expect(x => x.TableName, Is.Not.Set));

Anything that you can do on the main acceptance criteria can be done on the sub-criterias, and they can be as simple or complex as you need. This particular example expects either the type to be Example or the table name to be set.

Similar to Either is Any, which takes a params array of sub criterias, instead of just two.

Either actually just calls Any behind the scenes with just two arguments.

criteria.Any(
  sub =>
    sub.Expect(x => x.EntityType == typeof(Example)),
  sub =>
    sub.Expect(x => x.TableName, Is.Not.Set),
  sub =>
    sub.Expect(x => x.TableName == "tbl_Example"));

Of course, if you're purely using the single parameter Expect method then you could just use a regular or.

criteria.Expect(x => x.EntityType == typeof(Example) || x.TableName == "tbl_Example");

Extension methods

We like the simplicity of the single parameter Expect method, but sometimes you can't always do what you need in there very easily; .Net doesn't provide a lot of useful methods on IEnumerables that can be applied in the context of the criteria. We give you a few methods to help with that.

For collections we've added Contains, which takes a predicate instead of a concrete instance; this makes it easier to check if a collection has a matching element rather than an exact instance. There's also a plain string overload, which will match against whatever is deemed to be the identifier of a particular mapping (usually the name, but sometimes it may be the type if a mapping doesn't have a name).

Also for collections there's a IsEmpty and IsNotEmpty, which equates to collection.Count() == 0 but is a little shorter.

For everything else there's IsAny, which takes a params array of whatever type you're using it against, and returns true if any of the items are equal to the one you're calling it on.

Criterions

Criterions are what you use in the right-hand side of the two parameter Expect method. There's really only one available right now, which is Is.Set (and the Not inverse).

criteria.Expect(x => x.TableName, Is.Not.Set);

The criterions are just implementations of the IAcceptanceCriterion interface, exposed through a static class. You can quite easily create your own implementations if you have some custom behaviour that you find yourself repeating a lot.