erikdoe / ocmock

Mock objects for Objective-C
http://ocmock.org
Apache License 2.0
2.16k stars 606 forks source link

Add quantifiers to verify #302

Closed erikdoe closed 4 years ago

erikdoe commented 8 years ago

Planned for a long time (see #109 for example), this change contains several proposals how quantifiers could be implemented. Currently it is only possible to verify that a method has been called at least once (using verify) or that it was never called (using reject). With quantifiers it is possible to specify precisely how often a method should be invoked.

This proposal showcases three different options to implement quantifiers. This PR is meant as a basis to discuss the best option. It will only be merged once we've settled on one option.

The three options are: (for details see OCMQuantifierTests)

1) Creating the quantifier, passing it as an argument to a new verify macro. (Unfortunately, variadic macros won't work so we can't re-use the existing OCMVerify() macro for this.)

OCMVerifyQ([OCMQnt atLeastOnce], [someObject doStuff]);
OCMVerifyQ([OCMQnt atLeast:2], [someObject doStuff]);
OCMVerifyQ([OCMQnt never], [someObject doStuff]);

2) Using some macro/block trickery to avoid the "boring" part of the quantifier creation. This still needs a macro with a new name because it still has two arguments. Note that there won't be IDE support for discovering/typing this shorthand.

OCMVerifyQ(atLeastOnce, [someObject doStuff]);
OCMVerifyQ(atLeast(2), [someObject doStuff]);
OCMVerifyQ(never, [someObject doStuff]);

3) Using multiple statements. This looks a bit weird but means we could use the existing macro:

OCMVerify([OCMQnt atLeastOnce]; [someObject aMethod]);
OCMVerify([OCMQnt atLeast:2]; [someObject aMethod]);
OCMVerify([OCMQnt never]; [someObject aMethod]);

What do you think? Which option would you prefer? And why?

tarbayev commented 8 years ago

Actually variadic macros is still possible, see this link, but it's a bit bulky. Also, I'd prefer quantifier at second place, especially with universal OCMVerify version. BTW, why do you omit "exact" version of quantifier (e.g. [OCMQnt exactly:2])?

erikdoe commented 8 years ago

Thank you for the link. I had seen that the pre-processor itself can do variadic macros but I don't see a way how to use those for our purposes. The trick described in the answer you linked to may work. I just need to check how often the arguments to the top-level macro are evaluated.

Putting the quantifier second makes sense in case of variadic macros. For the statement-based approach it's not possible.

Once it's clear what path to take, I'd add more quantifiers, including the "exact" one you mentioned. I've only used a few so far to show how it would look.

bilby91 commented 7 years ago

I'm getting OCMock/OCMVerifier.h file not found :(

atom-wintermute commented 5 years ago

@erikdoe, hi! Can you merge this feature and release new version of library? It very useful for my project.

erikdoe commented 5 years ago

@atom-wintermute Theoretically yes. I'm still unsure which syntax to use, though. Do you have an opinion on which of the three options (listed above) is the most preferrable?

atom-wintermute commented 5 years ago

@erikdoe, I like the second option with shortlands most, it looks as concise as possible.

erikdoe commented 4 years ago

Thanks to @tarbayev I now found a much nicer solution. OCMVerify becomes variadic and as an optional argument before the invocation you can pass a quantifier. This can now be created with a variation of the factory as implemented previously. In total the invocations look like this:

OCMVerify([someObject doStuff]); // no quantifier
OCMVerify(OCMQ.atLeastOnce, [someObject doStuff]); // quantifier without args
OCMVerify(OCMQ.atLeast(2), [someObject doStuff]);  // quantifier with args

Unless someone objects in the next week or so, I'll add the remaining quantifiers, mainly an exactly quantifier, and then merge this into master.

erikdoe commented 4 years ago

To simplify the implementation and increase readability, I replaced the clever factory with plain macros. So, the syntax is now:

OCMVerify([someObject doStuff]);                // no quantifier
OCMVerify(atLeastOnce(), [someObject doStuff]); // quantifier without args
OCMVerify(atLeast(2), [someObject doStuff]);    // quantifier with args

This is similar to option 2 in the original version. I got a bit worried about name collisions, which is why there is a prefixed version of the macros, e.g. OCMAtLeastOnce() instead of just atLeastOnce(). The short macros can be disabled by defining OCM_DISABLE_SHORT_SYNTAX like in OCHamcrest.

By the way, I decided to have the quantifier as a first argument because that way it is more visible with long method invocations.