matcher to check if array contains an object matching a block
I recently wrote some code like this:
expect([array[0] someProperty]).to.equal(@"a");
expect([array[0] otherProperty]).to.equal(@1);
expect([array[1] someProperty]).to.equal(@"b");
expect([array[1] otherProperty]).to.equal(@2);
Now, I really don't care for the order of the elements of array, but couldn't find a better expecta-ish way to confirm both objects are present in the array.
I think for things like this, it would be nice to have a matcher that would make the code above a bit like this:
expect(array).to.containElementPassing(^(id o){
expect([o someProperty]).to.equal(@"a");
expect([o otherProperty]).to.equal(@1);
});
expect(array).to.containElementPassing(^(id o){
expect([o someProperty]).to.equal(@"b");
expect([o otherProperty]).to.equal(@2);
});
I'm not sure it could be implemented like this, and would also be happy if the block just returns a BOOL and that is what is used.
If something like this does make it into Expecta, it would also be nice to have something like containOnlyElementsPassing(). (Basically rubys all? and any?)
You can probably use https://github.com/specta/expecta/pull/90 and the equal or contains matchers dependent on your requirements. Is this what you are looking for?
Not exactly. The problem with using #90 and equal or contains is that they still only allow assertions about the elements in the array. I need to check properties of the elements in the array.
I do however like the idea of using any matcher on the elements of an array, and #90 would be of help if there were a matcher that tests wether an object conforms to a block-test. (And if any-semantics would be implemented).
If this block-test only returns a BOOL this matcher should be easy to implement, and my example would become something like this:
expect(array).elements.any.passTest(^BOOL(id o){
return [[o someProperty] isEqual:@"a"]
&& [[o otherProperty] isEqual:@1];
}
But it would (imho) be nicer if we could get something like this:
expect(array).elements.any.passTest(^(id o){
expect([o someProperty]).to.equal(@"a");
expect([o otherProperty]).to.equal(@1);
}
So the test-block itself doesn't return anything, but uses expect(...) itself.
Depending on the internal workings of Expecta (which I don't have the faintest idea about) the difficulty of implementing this might be somewhere between pretty easy and very hard.
For accessing properties you could easily just use KVC to get the properties
expect([array valueForKey:@"someProperty"]).elements.to.equal(@"a");
expect([array valueForKey:@"otherProperty"]).elements.to.equal(@1);
I think the second example would require a lot of additional complexity.
Shameless plug if you want to look at the inner workings and implement things yourself you could start by reading this for an introduction on how matchers work http://paul-samuels.com/blog/2014/03/16/how-does-it-work-expecta/
@paulsamuels that's almost what I need, but (aside from not looking that elegant, ymmv) has one flaw:
When I want to test wether the array contains two objects, each having specific properties, say object a with properties {x: 1, y:1} and object b with properties {x: 2, y: 2}, the valueForKey approach would also pass for an array like this: [{x: 1, y: 2}, {x: 2, y: 1}], so while it comes close, it does not do exactly what I want.
I'll take a look at your article and see what I can do :)
In that case the elements approach wouldn't work anyway as you have different expectations for each element in the array. Could you provide an example of how you would want that to work with multiple conditions
an example yould be this comment, assert that any of the elements in the array passes the matcher, then assert any element passes another matcher