hamjest
hamjest copied to clipboard
enhancement request: extend the "hasProperty" idea to arbitrary functions
the "hasProperty" matcher lets you convert from __.assertThat('hello'.length, __.greaterThan(3))
to assertThat('hello', hasProperty('length', __.greaterThan(3))
can we extend this idea to arbitrary functions?
so instead of writing __.assertThat(element.querySelector('option').value.length, __.greaterThan(3))
one could write __assertThat(element, evaluates(x => x.querySelector('option').value.length, __.greaterThan(3)))
if there is interest in this enhancement i would be happy to implement it. (I dont like the name "evaluates" and would happily pick a better name as soon as anyone has an alternate suggestions)
Hmm… interesting idea. The general problem with expressions like x.querySelector('option').value.length
is that you just get a TypeError
when one of the intermediate properties doesn’t exist or has a different type than expected.
That’s the core problem of most assertion libraries and that’s why all those expect(a.x.c).toBe(…)
assertions are (in my opinion) much worse than what hamjest, hamcrest et al can deliver.
That being said, I still don’t have a satisfying solution for function calls in the middle of such chains. I’ve recently added hasDeepProperties()
to simplify deeply nested assertions:
__.assertThat(element, __.hasDeepProperties({
something: {
value: {
length: __.greaterThan(3),
}
}
});
But there’s still no simple way to do function calls (with arguments) in the middle of such chains. We only have returns(…)
/throws(…)
for calls without arguments and context. Maybe your suggestion with evaluates(x => …, <matcher>)
is a good building block for this:
__.assertThat(element, __.evaluates(e => e.querySelector('option'), __.hasDeepProperties({
value: {
length: __.greaterThan(3),
}
}));
So far, I’ve simply done something like the following in these cases:
__.assertThat(element.querySelector('option'), __.hasDeepProperties({
value: {
length: __.greaterThan(3),
}
});
But that has the same problem mentioned above (causing a TypeError
if querySelector
doesn't exist, without a way for the assertion library to provide a meaningful description) and it gets worse if the function call is not at the beginning but deeply nested: something.other.querySelector(…).etc
And just as a side note: You can use hasSize()
to simplify your (and my) example and get better error descriptions:
__.assertThat(element.querySelector('option'), __.hasProperties({
value: __.hasSize(__.greaterThan(3)),
});
__.assertThat(element, __.evaluates(e => e.querySelector('option'), __.hasDeepProperties({ value: { length: __.greaterThan(3), } }));```
I can build "evaluates" and see what I can do for good messaging for the example above. I wonder if it would be helpful to allow an optional "function name/description" parameter that could assist with the messaging.
This also spawns another "syntactic sugar" matcher "hasNestedProperty". I would be happy to implement if there is interest.
__.assertThat(element, __.hasNestedProperty('value.length', __.greaterThan(3))
could be syntactic sugar for
__.assertThat(element, __.hasDeepProperties({
value: {length: __.greaterThan(3)}
}))
or maybe
__.assertThat(element, __.hasProperty(
'value', __.hasProperty('length', __.greatherThan(3))
))
I’m "sorry" to inform you that this feature is already implemented. 😊 The following should work as expected:
__.assertThat(element, __.hasProperty('value.length', __.greaterThan(3));
I just noticed that I had never documented that – fixed.
hasDeepProperties
is useful when you want to check more than one property at once (which happens in almost all of my tests).