Combinators

As your data structures get more complex ass won't be left behind. Coordinate expressions with or / and to fully express the assertion by composition.

Hint

fluent expectation chaining is implicitly evaluated as and.

ass(data).or(
  ass.string,
  ass.number.lessThan(12),
  ass.array.and(
    ass.size.equal(2),
    ass.contains('foo')
  )
)

Since some expectations will mutate the value for the rest of the expression, sometimes we may wish to go back to a previous version of the value. By using the .and combinator we can achieve that and express complex assertions with ease:

// Note: pluck mutates the value to be the one of the specific property for
//       all elements in the array.
ass(data).array.and(
  ass.size.above(10).below(100),
  ass.pluck('name').every.string.and(
    ass.not.empty,
    ass.match(/^[A-Z]/)
  ),
  ass.filter( ass.pluck('age').above(18) ).and(
    ass.size.is( ass.below(8) ),
    ass.all.prop('adult').true
  ).store(result, 'adults')  // this sets the intermediate value in result.adults
);

Here we've seen the power of composition, we've defined an expression that would certainly be quite a bit more complex if we did it in an imperative programming style. It takes a bit of effort to get familiar with the syntax but once you do it's hard to go back. Not only did the assertion get simpler but it should hopefully produce easy to understand failure messages.

Of course, this example was a little bit oriented towards testing, but the only thing that produces an assertion error is the ass(data). at the beginning, we could simply use ass. and pass the value at the end of the expression with .test(data) to get a boolean result. Even more, using .tap(fn) we can inject our logic anywhere in the expression and evaluate or mutate the values as they pass through, while with .store() we can keep the intermediate results around for further analysis.