A couple of days ago Ola Bini pulled my proposed ISpec mocking framework into Ioke (and did me the kindness of porting a change I made to Ioke/J into Ioke/C# – Number Infinity). It clocks it at around 300 lines of code (twice as many of test code) and I’m pretty pleased with the results, although several aspects of it could bear some refactoring.
In its simplest form, it allows you to add simple mocks and stubs by method name:
user stub!(:name) andReturn("Guybrush Threepwood") user mock!(:occupation) andReturn("Fearsome Pirate") anyNumberOfTimes
For more BDD-friendly syntax, it’s expected that you use
should receive syntax, which supports more advanced mock creation constructs:
user = User mimic user should receive(firstName: "Guybrush", authenticate!("Guybrush", "lechucksucks") andReturn(true)) user authenticate!(user name, "lechucksucks") should be true
The following method call count assertions are supported:
Return arguments given as a sequence will be returned in the sequence they are given for each subsequent call to the mock:
user = mock! name andReturn("Guybrush", "Elaine", "LeChuck") user name should == "Guybrush" user name should == "Elaine" user name should == "LeChuck"
The mocking support is still a work in progress. Significant omissions include friendly mock names, a more unified creation syntax, and more complex argument matchers (for example, Regex support: user should receive name(#/Guybr/)).
I had basically two goals in mind for the syntax of mock creation:
- I wanted something that played nicely and looked good with ISpec, and in this respect I drew some familiar syntax constructs from RSpec mocking.
- I wanted a great deal of flexibility. In this I drew far more from Mocha than I did for RSpec, whose mocking framework I find continually limiting.
But in particular, I wanted to use the power of Ioke’s macros to allow me to specify complex mocks in a more concise way. In Mocha, being a Ruby library, you can specify your mocks as simple key-value pairs in a hash, but if you want to build a more complex mock you must break off into a new line and start constructing it using separate calls. Ioke allows me to chain multiple expectations implicitly against the same should receive call, and even to apply the same expectation to an entire group of them:
user should receive(username, name, firstName) andReturn("guybrush") ; is equivalent to user should receive(username: "guybrush", name: "guybrush", firstName: "guybrush") user should receive(reload, save) anyNumberOfTimes ; is equivalent to user should receive(reload anyNumberOfTimes, save anyNumberOfTimes) ; is equivalent to user should receive reload anyNumberOfTimes user should receive save anyNumberOfTimes
I haven’t used the macro capabilities to their full power, and it shows in the code: much of it could reasonably be made much, much tighter through the use of macro syntax rather than simply leveraging the macro construct to manipulate conventional data structures. In particular the lack of that syntax manifests itself in a dismaying lack of consistency between different syntaxes for constructing mocks and stubs; unifying that is the number one thing I want to improve, structurally, in the framework.
But, most importantly, I haven’t yet used the framework in anger for anything; simply building it has consumed most of my Ioke hacking time. I suspect that it would benefit a great deal, as everything does, from real-world use; although I am an experienced tester with mocks, I am not yet an experienced user of my own. If you’re embarking on an Ioke project and need to test with mocks, please do let me know.