expecta icon indicating copy to clipboard operation
expecta copied to clipboard

matcher to check if array contains an object matching a block

Open ahti opened this issue 11 years ago • 6 comments

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?)

ahti avatar Mar 31 '14 02:03 ahti

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?

ollieatkinson avatar Mar 31 '14 07:03 ollieatkinson

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.

ahti avatar Mar 31 '14 22:03 ahti

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 avatar Mar 31 '14 22:03 paulsamuels

@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 :)

ahti avatar Apr 01 '14 11:04 ahti

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

paulsamuels avatar Apr 01 '14 11:04 paulsamuels

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

ahti avatar Apr 01 '14 11:04 ahti