cedarbdd / cedar

BDD-style testing using Objective-C
http://groups.google.com/group/cedar-discuss
1.19k stars 140 forks source link

Checking that a method was called twice with different values #326

Open JamieREvans opened 9 years ago

JamieREvans commented 9 years ago

I'm trying to verify that a method gets called twice with specific values, but I can't seem to verify both calls, just the first. I have verified that the method is called twice and that the values are correct, but I'm not sure how to write the cedar spec.

Here is what I have:

        it(@"should call sleep with time intervals of 0 and 5", ^{

            // subject is a spied on object
            subject should have_received(@selector(someMethod:)).with(0); // Passes
            subject should have_received(@selector(someMethod:)).with(5); // Fails
        }  

The error I'm getting is this:

Expected <MyObject> to have received message <someMethod:>, with arguments: <5> but received messages:
  someMethod:<0>
  someMethod:<5>

Also, it doesn't seem you can verify that a method was called a specific number of times without overriding the invocation and tracking the count.

idoru commented 9 years ago

There were some recent changes to be more strict with passed types. What happens if you cast the argument to with() to the exact numeric type being passed to the method?

Yes, there currently isn't a matcher for this, but it certainly would be a welcome addition. For now I have filed a feature request in Cedar's public tracker: https://www.pivotaltracker.com/story/show/94038932

Alternatively, you can perform the assertions more directly on the messages captured by any double object by accessing them via [(id<CedarDouble>)double sent_messages]

JamieREvans commented 9 years ago

I was able to get the sent_messages and use that to verify that I was getting the correct values, but there really should be a better way to validate multiple messages and their parameters.

Thanks for the help.

akitchen commented 9 years ago

I agree, this should be supported. I just tried to reproduce the issue you described, but wasn't able to do so. What is the declared type of the argument in your example?

Here are the specs I added in HaveReceivedSpec.mm in Cedar's own spec suite:

it(@"should pass for multiple invocations with different arguments", ^{
    [incrementer incrementByNumber:@666];
    expect(incrementer).to(have_received(method).with(expectedParameter));
    expect(incrementer).to(have_received(method).with(@666));
});

//...

it(@"should pass for multiple invocations with different arguments", ^{
    [incrementer incrementBy:666];
    expect(incrementer).to(have_received(method).with(expectedParameter));
    expect(incrementer).to(have_received(method).with(666));
});
akitchen commented 9 years ago

Oh, I realized it right after posting. It appears to be an NSTimeInterval, which is typedef double

This probably boils down to a failure to match floating point numbers. The equality matcher has the same problem, which is the reason for be_close_to().within()

akitchen commented 9 years ago

As to the other question, how about have_received(selector).withCount(5) ? I guess I haven't missed not having this, but it seems like it could be useful

tooluser commented 9 years ago

Would it be possible to extend the have_received function with an index, so that the .with() behavior worked as well?

I think we may have written (not gracefully) a wrapper of some kind to do this at one point.

On May 6, 2015, at 21:36, Andrew Kitchen notifications@github.com wrote:

As to the other question, how about have_received(selector).withCount(5) ? I guess I haven't missed not having this, but it seems like it could be useful

— Reply to this email directly or view it on GitHub.

JamieREvans commented 9 years ago

@akitchen Does your example check different values? If expectedValue is @666, the test would definitely pass. I'm talking specifically about having two different values - the same method called multiple times with different parameters.

akitchen commented 9 years ago

They are different values. these code snippets were additions to cedar's own test suite.

On Thu, May 7, 2015 at 6:47 AM, Jamie Riley Evans notifications@github.com wrote:

@akitchen Does your example check different values? If expectedValue is @666, the test would definitely pass. I'm talking specifically about having two different values - the same method called multiple times with different parameters.

Reply to this email directly or view it on GitHub: https://github.com/pivotal/cedar/issues/326#issuecomment-99872677

idoru commented 9 years ago

Adding a method that takes an NSTimeInterval to SimpleIncrementer and this snippet in HaveReceivedSpec.mm reproduces the error:

    context(@"with a time interval", ^{
        fit(@"should get types right", ^{
            [incrementer incrementByTimeInterval:0];
            [incrementer incrementByTimeInterval:5];

            //fail
            incrementer should have_received(@selector(incrementByTimeInterval:)).with(0);
            incrementer should have_received(@selector(incrementByTimeInterval:)).with(5);
            //pass
            incrementer should have_received(@selector(incrementByTimeInterval:)).with((NSTimeInterval)0);
            incrementer should have_received(@selector(incrementByTimeInterval:)).with((NSTimeInterval)5);
            //also pass because NSTimeInterval is double
            incrementer should have_received(@selector(incrementByTimeInterval:)).with(0.0);
            incrementer should have_received(@selector(incrementByTimeInterval:)).with(5.0);
        });
    });

The literal 5 is implicitly an int. In cases like NSTimeInterval you can get away with adding a .0 to get an implicit double. Unfortunately for other types such as CGFloat which is double on the 64-bit iOS devices and float on 32-bit ones I've been forced to cast to CGFloat to keep specs passing on all devices. It's worth noting that for sanity I've found one should avoid float literals (unless for some reason you're only building for 32-bit) for CGGeometry expressions (CGRect et al) and then you're also more likely to get success with the equals matcher instead of relying so heavily on `be_close_to() for those truly equal cases.

codeman9 commented 9 years ago

I like the idea of having this have_received(selector).withCount(5) option.