Ancient City Ruby 2013

Building a mocking library

This talk is not about testing, nor is it really about mocking. However, analyzing a mock object library is a great way to showcase advanced Ruby topics. Have you ever wondered exactly how a mock object library does what it does? You can understand it!

This talk uses a simplified mock object library as the basis for delving into topics such as metaprogramming and the Ruby object model. The goal is to increase the knowledge of these topics in the Ruby community. With this know--how, you will be better suited to build from and contribute to common Ruby tools that use them.

Ancient City Ruby 2013

00:00:00.240 good morning i'm andy lindemann you can follow me online in various places at a lindeman i work
00:00:06.640 for big nerd ranch and i really enjoy working there big nerd ranch writes high quality books
00:00:11.840 we also provide training and do consulting i'm also a maintainer of rspec a ruby
00:00:17.359 testing framework you might have heard of i'm also writing a book called upgrading to rails 4 so check that out if you're
00:00:23.840 interested in rails 4. but this morning we're going to be talking about building a mocking library or building a mock object
00:00:30.000 library this is a hands-on coding talk so make sure you're strapped into your seats
00:00:35.280 and i think you'll really enjoy it
00:00:40.800 so mocking mocking libraries are a tool that many of us use in the ruby community but i don't think
00:00:45.920 we always understand how they work methods magically appear when you write things like object.stub or object.stubs
00:00:54.480 methods magically disappear at the end of test the stubs automatically get cleaned up when you set things like mock
00:01:00.239 expectations which some other speakers have talked about like object.should receive or object.expects
00:01:06.080 depending on your framework these things magically get verified at the end of tests but i think it's worth knowing how the
00:01:12.400 tools you use work under the hood and how to build simple replicas of them and that's what we're going to do this morning we're
00:01:18.240 going to build a mock object library and i hope you're surprised at how many tools the ruby language
00:01:24.640 gives you to build one of these pretty easily okay we're going to take a step back and talk a little bit about
00:01:30.240 what the syntax of the library that we're going to build today looks like i already decided
00:01:35.840 this syntax and it's nothing exactly like anything that exists currently though as
00:01:42.079 i'll speak to later our spec may support this syntax relatively soon
00:01:49.200 so imagine you have a warehouse test double a warehouse being an object that knows
00:01:55.439 how to store items how to know if items are included in this warehouse
00:02:00.799 imagine we have a production warehouse code object but in tests we're going to fake it out and use a test double
00:02:07.040 nell hit on test doubles yesterday if you want to set a stub just a just a
00:02:13.120 a kind of replay of what warehouse should respond to if we wanted to say hey warehouse act
00:02:19.040 like you're full respond to the full question mark method with true we're going to write some
00:02:25.760 syntax that looks like this allow w to receive the full message
00:02:30.800 and return true so then at any point in the test after that we can call w dot full and get back true
00:02:36.640 if we want to set a mock expectation again in the library we're going to build today we're going to say something like expect
00:02:42.560 w to receive the remove message with a given item id and that says
00:02:48.560 hey we better receive w dot remove one two three four it better be
00:02:53.760 called at some point during the test that's a mock expectation are you with me
00:02:59.120 awesome let's talk a little bit about what that syntax
00:03:06.159 compiles to kind of underneath the hood we have this nice looking dsl here but
00:03:11.360 we're going to just we're going to have objects underneath the hood when you build a dsl like this it's a good idea to just
00:03:16.879 immediately delegate to some object layer underneath you it makes it a lot easier to test and it makes it a lot easier to reason about
00:03:23.920 okay so what does something like allow w return we're going to call that a stub target in our system it's going to wrap the w
00:03:31.040 object and allow it to have stubs put on it so we're going to call this stub target
00:03:36.720 and you'll see it when we get into code that all we do is just call stub target dot new when you say allow this part over here
00:03:44.159 to the right this fluent interface this chained interface is going to build up an object i'm going to call an expectation
00:03:50.480 definition it builds up a information about the method that's about to be stubbed
00:03:57.200 things like the name and the return value so i could write code that looks like this and have it have the same
00:04:02.879 effect and again this is a good idea when building a dsl a domain specific language
00:04:08.640 that really you can construct the thing underneath it relatively easily and the dsl is just a
00:04:14.159 nice layer on top of it okay i'm going to have some audience
00:04:21.519 participation in this in this slide let's talk about ruby's method dispatch if i have an object called o and i call
00:04:28.320 2s on it how does ruby know what code to run
00:04:33.520 how does it find 2s anybody what was that it starts looking in the
00:04:39.919 current class yeah it starts looking at the current class but it could go up to super classes
00:04:46.400 right ruby calls this the ancestor chain and you can get at it by calling
00:04:52.280 o.class.ancestors and it returns you a list that's meant to be read from left to right
00:04:57.600 i got that right left to right it's right to left for me
00:05:05.120 so it starts looking at object and says hey object do you have a 2s method and in fact object does so that's the code
00:05:10.479 that gets dispatched but what if i sent in a message like the magic id method the underscore underscore id underscore underscore
00:05:16.560 method that's not on object it's on basic object well philosophically what would happen is ruby would start at the left
00:05:22.800 say hey object do you have a magic id method nope kernel nope basic object yep and that's the
00:05:29.280 code that would get invoked except i left a little detail out
00:05:35.360 the singleton class it's not listed here but objects in ruby have singleton
00:05:40.720 classes because ruby allows you to define methods that are only on one object
00:05:47.280 if i write some code that looks like this o dot define singleton method giving the symbol hello and returning a
00:05:53.360 block hello bang string i can call o dot hello and get back
00:05:59.759 hello but no other object in the system has a hello method only o if i construct a new object it
00:06:07.199 does not have hello and the method by which this happens is ruby defines
00:06:12.639 these singleton methods on the singleton class for specific objects and each object has a different singleton class
00:06:19.520 when we're going to build our mock object library we'll be we'll be manipulating objects on
00:06:24.800 singleton classes for objects so that's all i want to talk about before
00:06:31.280 we dive into some code you all ready for this yes
00:06:37.680 all right here's my terminal it's white actually not black
00:06:45.840 we're going to make our mock object library a gem and it's going to be called ancient mock
00:06:52.960 the bundle gem command just creates some files that will be useful when building a gem
00:06:58.319 it's just some scaffolding so let's cd into the directory
00:07:03.840 and open up vim because everyone here uses vim so everyone will be familiar with that right
00:07:09.759 i was really surprised actually yesterday at how many people were using vim and tmux which is my setup if you're not familiar with it i'm not going to do
00:07:15.440 anything too tricky but that's my editor of choice okay let's look at a file let's look at
00:07:20.560 the gem spec to start off with it's got some details that i would want to fill out if
00:07:26.319 i were actually publishing this gem but of course i'm not so i will just leave them blank
00:07:31.520 but it does have things like development dependencies down at the bottom and i'm going to add a new development dependency for mini test
00:07:38.000 now you might be asking yourself why is an rspec maintainer using mini test and it's mostly because i'm using some
00:07:43.440 syntax that currently conflicts with the way our spec is built because rspec may eventually support the syntax i showed
00:07:49.680 i would conflict with expect so i'm using mini test it's a perfectly great testing library
00:07:54.800 and that's just to make sure i don't run into some some weird edge cases okay let's look at a file in the lib
00:08:01.280 directory that was created for us by the bundle gem command it's just four lines or five lines of code here one of them
00:08:08.240 brings in a version constant i'm going to change this to require relative so i don't have to deal with as
00:08:13.280 many load path issues in the tests and then it creates a place for us to put some code and that's where we'll be
00:08:18.800 putting code now i told you i was going to use minitest and do this in a test driven development fashion so
00:08:25.440 i hope you learned something about my workflow when writing tests and we are going to write this test first
00:08:31.120 so let's open up a file in the test directory you can see this down here i'm just opening up test ancient mock test
00:08:39.120 i need to create the test directory and save the file let's write some
00:08:45.040 scaffolding code that we just need to get some stuff running let's pull in mini test spec mini test
00:08:50.640 auto run so this file just runs as a regular ruby file and let's require relative the file from the lib directory
00:08:58.080 called ancient mock that brings in our gems code if we've hooked up everything correctly
00:09:03.839 we can write something like describe ancient mock do and then this is where we're going to put our tests this looks really similar
00:09:10.240 to our spec this but it's mini test spec i have a key sequence in my editor to
00:09:15.279 run tests and you'll be seeing this quite often so let's see it work running the tests and there's no tests
00:09:21.519 but at least we can see that we've hooked up everything correctly let's write the first test it allows an
00:09:29.360 object to receive a message returning a value we'll say
00:09:34.720 this is the simple stub case that we saw on the slides let's create a warehouse and just make
00:09:39.839 it an object.new again warehouse is just a test double we're going to use throughout because it's an easy
00:09:45.120 thing that hopefully everyone understands warehouses have items and you can manipulate them let's write
00:09:51.200 allow warehouse to receive the full question mark message and return true this is the syntax that
00:09:59.440 we defined in our slides if this test does what we want it to we
00:10:05.040 should be able to call warehouse.full and it must equal true
00:10:10.240 i was interested in in the talk yesterday because i kind of just without even thinking
00:10:16.240 kind of separate the given when and then portions of my test by white space so i just kind of
00:10:22.399 do this automatically but i found it really interesting that it might be a good idea to formalize these things but basically given a
00:10:28.880 warehouse allow it to receive a message then test that it can actually receive that message
00:10:34.560 okay let's run the tests and the first thing it blows up because there's no method called allow undefined method allow
00:10:42.240 for this test class it looks kind of weird but that's just the mini test spec dynamically created class all right let's fix that let's switch
00:10:49.760 back over to the production code file and define a module
00:10:54.959 called test extensions test extensions is going to have these methods like allow
00:11:00.320 and allow takes in some object and like we said on the slides it's just going to immediately construct a stub target
00:11:07.040 what does stub target do well for now let's just define it to take in an object in its constructor and save it off as an
00:11:13.839 instance variable okay down here at the bottom let's
00:11:19.680 reopen the mini test unit test case class and include ancient mock test extensions
00:11:27.279 so this brings in the test extensions module which has things like allow and we'll eventually have methods like receive and expect
00:11:34.880 all right let's run the tests we're a little bit farther along now undefined method receive so it's
00:11:40.880 ruby's trying to evaluate the right side of this expression this part over here
00:11:46.800 let's fix that by switching back over to the production code and defining a new method called
00:11:54.160 receive that takes in a message name like full question mark like we said on the slides this is going
00:11:59.760 to start constructing an expectation definition and we'll pass in the message
00:12:05.920 name in the constructor let's define expectation definition
00:12:12.560 have it taken a message name in its constructor save it off to an instance variable and we'll actually expose this
00:12:18.399 as an adder reader so that things from the outside can say hey expectation definition what message were you defined with let's
00:12:25.839 run the test again we're a little bit farther now and return is undefined
00:12:30.880 and this is that this is where some neat stuff starts happening the tests are actually telling me exactly what code i need to write basically
00:12:36.959 there's an and return method it's undefined and it's undefined on expectation definition so that's where i need to put it
00:12:44.720 and return takes in a return value saves it off as an instance variable
00:12:52.240 we'll also expose the return value oops as an outer reader
00:13:00.320 and importantly we need to return self from and return this is pretty standard when you're doing a fluent interface in an
00:13:06.480 object-oriented language you keep building up an object and return self every time to support chaining on more methods so
00:13:13.519 because we're writing code that looks like receive and return and eventually we'll have receive with and return
00:13:20.720 you return self every time so the object just keeps getting built up okay let's run the tests now there's an
00:13:28.800 undefined method to for stub target that's the next method we need to write
00:13:34.160 this is if you look at the test what's happening is allow is returning a stub target and we're
00:13:39.680 trying to invoke the to method on it to make that stub happen going back over to the production code
00:13:46.320 let's scroll up here to stub target and define to now to is getting called
00:13:53.680 with one argument an expectation definition and we'll just shorten it to be definition as a local variable here
00:14:00.480 now what does it need to do well it needs to define a method right and to do that i'll use the syntax i
00:14:07.760 showed in the slides obj.define singleton method it the first argument is the method name
00:14:14.720 i want to define again i'm defining a method dynamically here on the singleton class so definition.message that's the message
00:14:21.120 name and what's the implementation well just the return value just give back the return value
00:14:29.279 if we've hooked up everything correctly we have our first passing test we've stubbed a method on an object
00:14:36.480 in about you know five minutes or so and in about 42 lines of code
00:14:41.760 except i hope some of you are a little uncomfortable we've just defined a method on an object and left it there
00:14:47.120 most mock object libraries clean up after themselves and ours is not this is best demonstrated by
00:14:53.040 yet another test right so let's write it another test to demonstrate the problem it removes stubbed methods after tests
00:15:00.320 finish let's create another warehouse object
00:15:06.160 let's allow it to receive the same message this is the line from above let's
00:15:14.880 make up a new method on our ancient mock module called ancient mock.reset
00:15:20.000 that will reset the state back to what it was previously most frameworks
00:15:26.079 have a like a method like this our spec calls it reset as well and the implementation is going to be
00:15:31.759 something like hey go back to the state before you started stubbing things on things so go back to the point before
00:15:37.920 we had a full method so if we try to invoke full on warehouse what should happen
00:15:45.600 exactly so we'll use assert raises to say hey if we try to call warehouse.full here it
00:15:52.560 should raise an exception all right let's run the tests undefined
00:15:57.680 method reset for the module so that tells us where to put the code let's switch over to the
00:16:03.120 production code we'll put it toward the bottom here and since it's a module method i'll use defself.reset that puts it at the module
00:16:09.839 level and i'm not sure what to do here and i'm not sure what to do here
00:16:15.279 because if you look back up at stub target when we define a stub
00:16:22.000 we're just defining a singleton method and going on with life we actually have no registry of methods that were stubbed we
00:16:29.519 don't even have a list of methods that we stub so it's pretty much impossible to do a reset right now because we don't know which methods are
00:16:35.279 library stubbed and which ones are just there beforehand we could probably do some crazy ruby magic to figure this out but it's not
00:16:41.759 what i'm trying to demonstrate what we need is some sort of separate class that defines stubs but also keeps
00:16:47.759 track of what stubs exist so because i am such a creative
00:16:53.120 namer i'm going to name a new class called stubber that is initialized with an object
00:16:59.360 that saves it off as an instance variable and it has a method called stub that also takes in
00:17:05.120 a definition and we're going to move the code that's currently up here in stub target
00:17:10.640 down into the stubbor class and instead of in stub target doing the
00:17:16.880 definition of ourselves we're going to create a new stubber and delegate down into the stubborn
00:17:23.280 class to do the stub now right now we've done functionally nothing different the code works exactly
00:17:28.799 the same and in fact we can verify this by running the tests again and seeing that the first one still passes
00:17:35.360 but the second one is still failing of course but it's failing for a slightly different reason now because we defined
00:17:41.520 the reset method we expected that there was a no method error to be raised but nothing was raised so we've got a little bit of
00:17:48.480 work to do still but at least we've gotten a refactoring that didn't break entire portions of the
00:17:54.160 system okay so stubborn stubborn.stub is going to do
00:17:59.840 something a little different it's actually going to save the the list of things that were stubbed
00:18:05.520 in an array called definitions so it's going to shovel on each definition before it stubs it so it
00:18:12.640 keeps state around about what methods were stubbed definitions is just going to be an empty array initialized
00:18:17.840 in the constructor okay this is closer but we still don't have a list of all the methods that were
00:18:23.919 stubbed because this stubborn instance immediately goes out of scope and gets garbage collected
00:18:30.080 well so instead we're going to create a custom constructor at the class level of
00:18:36.000 stubborn that saves the stubbor instance in a list i'm going to name it for object and it's
00:18:42.559 going to take in an object and it is going to construct a stubber for that object but it's going to save it off in a hash
00:18:49.679 keyed by the object's id and it's going to lazily create it and save it for
00:18:54.960 for each object one object there's a one to one between an object and stubber this code looks a little
00:19:01.919 weird but it's actually pretty much straight from our spec our spec does something really similar to this our spec calls them mock proxies
00:19:08.400 though but it stores them in a hash keyed by object id well we need to
00:19:13.520 define stubbers stubbers is just at the class level going to be a lazily initialized hash
00:19:19.919 now instead of constructing a stubber up here with dot new we construct it with four object so that
00:19:25.840 does the construction returns an instance and also saves it in a list
00:19:30.880 this doesn't get garbage collected because the stubborn class doesn't go away as opposed to stock
00:19:36.080 right since we're defining stuff at the class level um this is kind of basic with
00:19:41.120 effectively global state but it's okay for now
00:19:46.960 i'm not trying to do everything correctly or else this talk would be five hours long
00:19:52.400 okay what do we want to do now we want to have one method to call to just reset everything
00:19:57.440 at once let's make up some code that we wish existed at the class level of stubber
00:20:03.440 called self.reset so we can call reset on stubber and just everything gets cleaned up what
00:20:09.679 code would we have to write to make that happen well we'd want to tell each stubber to go reset itself
00:20:15.679 and we don't have a method to do that yet but let's just assume that we're going to create one i do this all the time just like i will
00:20:21.440 write a reset method later so loop through each stubbor instance that was saved
00:20:26.720 in that global hash and reset it and then also clear out the list of stubbers
00:20:31.760 because the list of stubborns is no longer valid after they reset themselves okay i know you're
00:20:37.200 thinking what does reset do let's get there now at each instance of stubber we have a list of definitions a
00:20:43.039 list of stubs what should reset do well it should go through each of them and remove them
00:20:49.679 so let's loop through each definition and unfortunately there's not
00:20:56.880 any public way that i know of to remove methods from a class so we're going to have to get a little weird here but that's okay because we're
00:21:02.720 a mock object library we're allowed to get dirty we're going to clap we're going to evaluate a block of code
00:21:09.280 in the context of of obj's singleton class this is effectively just writing code
00:21:14.559 between class statements in ruby so any code that that gets run here is kind of run in the
00:21:21.520 context of obj.singleton class and we can write private methods because we don't have to
00:21:27.679 give an explicit receiver anymore because self in this block is obj.singleton class so we can just write remove method
00:21:34.200 definition.message if it was defined if method defined definition.message so remove the method
00:21:41.840 if it was defined okay it's been a while since we've run
00:21:47.360 the test it's making me a little nervous so let's run them and nothing is still happening yet
00:21:54.159 well we're still getting a failure because no exception was raised we've built up all these reset methods
00:22:01.120 but we haven't called them in the one reset method that really matters ancient mock.reset so let's go down to
00:22:06.880 the end of the file and call stubbor.reset here stubbor.reset if you'll know goes
00:22:13.360 through each stubber resets it and clears everything out
00:22:19.120 running the test we have two passing tests so we have the stub and the unstub case
00:22:26.480 all right except there's a third case right what if the method existed already what
00:22:32.400 if there were already a full question mark method that was defined elsewhere we're just removing it right we're just
00:22:38.320 we're so we're killing state that existed already on the objects again this is best recognized as a test
00:22:43.840 let me write that hopefully it'll make sense it preserves methods that originally existed
00:22:50.960 let's create a warehouse object as usual let's define a method on the warehouse singleton class this is yet another way
00:22:57.520 to define methods on ruby singleton classes this defines the full method to return false so the warehouse is initially
00:23:03.760 false is initially not full full returns falls let's stub it as true
00:23:13.039 let's reset and we expect it to go back to false
00:23:20.799 so it was originally full before the stubborn got involved stubbed it as true reset should go back
00:23:27.440 to false except you know you guys know what's coming right a no method error because we just nuked it just
00:23:34.799 we just took it away no matter what existed there before so let's fix that let's go back over to
00:23:40.960 the production code let's go down to the def stub method
00:23:49.200 the def stub method does not care what existed beforehand it just defines a singleton method and
00:23:54.799 if there was already a singleton method it's just gone so really what we want to do is before
00:24:01.279 defining a singleton method we want to preserve something preserve the method if it already exists
00:24:07.600 how do we do that we can ask the singleton class does a method already exist on you with this name
00:24:13.200 if oej.singletonclass dot method defined question mark
00:24:18.320 for the name of the method definition.message hey if you've already got a method defined on you let's get
00:24:25.520 a method object that represents that code ruby doesn't necessarily have first
00:24:31.679 class functions but it does have method objects you can actually ask ruby for an object representation
00:24:37.360 of your method and you get at that by saying object.singletonclass.instancemethod
00:24:43.880 definition.message this returns a method object that looks very similar to a proc or a lambda
00:24:49.679 except it was created via the implementation of the method and let's save that off in an array in
00:24:56.000 an instance variable called preserved methods where preserved methods is initialized
00:25:01.760 in the constructor to be an empty array so if a method was already on the class that we're
00:25:10.240 about to run over we preserve it now down here in reset after we've
00:25:17.440 removed the methods we really want to loop through the ones that were preserved and redefine them
00:25:23.760 as their old implementation so let's do that preserved preservedmethods.each do
00:25:29.279 method a small nuance here we really want to we really want to restore them in
00:25:36.640 reverse order ordinarily i would write a test for this and it's a great candidate for a test but because i want to show you guys so
00:25:42.559 many things i'm going to skimp on test coverage a little bit here but otherwise if you stop thinking something multiple times
00:25:48.320 the wrong implementation would be restored if we didn't reverse do them in reverse order because we're building up the preserved methods list
00:25:55.279 from left to right we want to restore them from right to left okay so that's that's what that means
00:26:01.679 now when you have a method object you can also use an alternative form of
00:26:08.559 define singleton method to just redefine the method with a
00:26:13.919 method object so ins while up here we defined a singleton method with a block
00:26:19.760 of code down here we can just redefine the method by passing the method name
00:26:25.520 and the method object itself into define singleton method so ruby's instance method method that
00:26:31.760 gives back a method object pairs really well with define method and define singleton method who can also take a method object to
00:26:38.640 redefine the the method with that method object that we originally saved
00:26:44.960 say that again yeah not happening
00:26:50.000 all right let's run the test we have three passing tests we've restored the implementation of the the methods uh properly
00:26:57.919 so we have stub unstub and then unstub where there was original method that we need to
00:27:03.360 keep around all right that was all right so in 15 minutes we've gotten a pretty sweet mocking library
00:27:09.279 or at least a stubbing library we don't have mocs yet though right mocks are expectations where we expect things to
00:27:15.840 get a messages during the test so let's try that it expects that a message will be
00:27:23.919 received let's have a warehouse
00:27:29.120 let me move this toward the top and let's write instead of allow expect expect warehouse to receive
00:27:36.880 we'll say something like the empty method maybe the empty method says hey warehouse clear out all your
00:27:42.240 inventory so we're expecting that in the course of this test warehouse.empty had better be
00:27:48.080 called but we're not going to call it so when we call ancient mock.reset
00:27:53.120 what should happen an error we're going to make up an error name ancient mock expectation not satisfied
00:28:02.960 so calling reset here because we did not call warehouse.empty warehouse.empty was
00:28:08.080 not called then an error will be raised by the reset method when it loops through each expectation and verifies that it
00:28:14.640 actually got the message that we expected all right let's run the test the first
00:28:20.480 thing that's wrong is there's no method called expect we haven't added it to our test extensions
00:28:26.320 so let's do that flipping back to the production code the test extensions is toward the bottom
00:28:31.840 here let's add a new method called expect that takes in an object but instead of a stub target let's call
00:28:37.840 it an expectation target because it's the target of a mock expectation but it's really
00:28:43.679 really similar to stub target so similar in fact that i feel pretty comfortable
00:28:49.039 making expectation target a subclass of stub target because mock expectations in our library
00:28:56.080 are still stubs the method implementation still gets stubbed out but also we verify it
00:29:02.480 so we inherit stub target's constructor and also the the two method but we want to overwrite
00:29:08.960 it to call into the super class's implementation of to
00:29:14.240 which does this but also to add the definition to a list of expectations that gets
00:29:20.480 verified so this is a specialized form of a stub it's a stub that gets added to a list of
00:29:27.200 expectations to later be verified now expectations doesn't exist so let's create that
00:29:32.960 go to the bottom of the file here and say def expectations is just a lazily initialized array
00:29:41.120 and it's a list we can use to verify those stubs that are also mocks that need to be verified
00:29:46.880 now let's verify we haven't broken anything we haven't but our test did get a little farther
00:29:52.320 now it's an uninitialized constant expectation not satisfied well that's easy enough to fix let's go
00:29:57.360 to the top of the file and define expectation not satisfied
00:30:03.760 is a new class that inherits from standard error it's just an exception it's a runtime error
00:30:11.440 all right run the test again isn't it great when you have fast tests you can just keep running things it
00:30:16.559 tells you where to put things all right now we actually get the failure we're looking for
00:30:21.600 expectation not satisfied was expected to be raised but nothing was raised so the block of code reset
00:30:28.000 is not raising an error even when our mock was not satisfied and it kind of makes sense right we
00:30:33.120 haven't written any new code in self.reset down here it's just resetting stubs it's not verifying expectations
00:30:39.840 so let's fix that i'm thinking that it makes sense to have some sort of verify method on each definition that verifies
00:30:46.720 that it was called so let's just loop through each expectation and call something like verify on it
00:30:53.520 now we haven't written verify but we will soon and we're thinking hey verify will probably raise an exception
00:30:59.760 if an expectation was not verified so we need to make sure that even if an exception is raised here let's still
00:31:04.960 reset all the stubbers because it'd be really annoying if your tests failed due to mock expectation errors and then also stubs
00:31:11.760 just happened to stick around you would get some weird non-isolated tests so let's ensure that everything gets
00:31:17.760 reset on top of that we actually want to clear out the list of expectations as well so that we don't get weird feedback
00:31:24.000 between tests so even if one of the expectations fails we still clear ourselves out and go back to
00:31:29.519 normal let's run the tests and we've broken it in a new and interesting way
00:31:35.679 no method verify for expectation definition we tried to call a method that didn't yet exist on expectation definition
00:31:43.360 all right that's easy enough to fix let's go to expectation definition and write a verify method can anyone
00:31:51.039 think of a good implementation for verify i can let's just raise an exception
00:32:00.799 did anyone note that like i can make the test pass with that so i know there's something else here i did like and we intuitively know there's
00:32:07.279 something else right but since i've been running the test constantly i can just write small bits of code and actually see that
00:32:13.279 there's a gap in coverage here because all my tests passed but that's clearly not right
00:32:18.320 so let's fix that by writing a new and interest more interesting test but i'm going to leave the production code like that because
00:32:24.960 it's clearly a gap in coverage okay the new and interesting test is it does not raise an error
00:32:31.760 if expectations are satisfied let's create the warehouse let's expect
00:32:38.559 that it receives a message empty and then let's call it and then calling ancientmoc.reset should
00:32:45.600 not raise any errors so we can just write it out in the test and if it raises an exception the test will fail
00:32:52.399 running the test they do in fact fail with because an ancient mock expectation
00:32:58.880 not satisfied error was raised all right let's go back to the implementation
00:33:04.559 really what i'm intuitively thinking here is something like if invocation count is not equal to one
00:33:11.360 maybe most mocking object libraries allow you to customize the number of times you expect to receive a message but we're
00:33:16.960 just going to hard code it as one for now that'll be ancient mach 2.0
00:33:22.320 but we and and then where invocation count starts out as zero in the constructor
00:33:30.559 but we don't have any way to increment invocation count and in fact if we look at the place where we define the method
00:33:36.640 down here on line 52 all we do is just cough up the return
00:33:42.480 value we don't give definition an opportunity to increment that invocation count
00:33:48.159 so i'm going to change definition to instead of just coughing up the return value i'm going to change it into a callable
00:33:54.159 object it's pretty standard practice in ruby if you want an object to act like a callable piece
00:34:00.240 of code like a proc or a lambda to just give it a call method definition doesn't yet have a call
00:34:06.080 method so let's add a call method
00:34:12.639 call is going to look something like still cough up the return value at the end
00:34:17.839 but also increment invocation count so this gives definition an opportunity
00:34:23.280 to increase its own invocation count while still returning its return value
00:34:29.599 all right let's run the tests and we have five passing tests so we've got stubs and mocks basic ones
00:34:35.440 anyway but we don't have the ability to pass arguments to stubs we don't have any way
00:34:40.720 to say that a a method should receive arguments or receive certain arguments so let's write
00:34:47.599 a test for that it allows objects allows objects to
00:34:53.520 receive messages with arguments let's create a warehouse
00:34:59.680 and this is going to be a really interesting test actually we're going to allow the warehouse back to stubs for now allow the
00:35:06.320 warehouse to receive a message called include question mark which tests if the warehouse includes a
00:35:11.520 given item by its id so we're going to say we're going to stub out the warehouse to say
00:35:17.839 the warehouse does include an item with id1234
00:35:23.200 but it does not include an item with id 9876 so that one will return
00:35:30.560 false so include one two three four true include
00:35:36.880 nine eight seven six false so if we call warehouse.include with one
00:35:42.560 two three four it should equal true if we include
00:35:49.680 on 9876 that should be false
00:35:57.119 running the test the first problem is that we've made up this with method but it doesn't exist and it doesn't exist on expectation
00:36:03.599 definition let's define it there we're already in expectation definition here so we can add a new method
00:36:11.040 with that takes in a list of arguments and we're going to use the splat operator to make the argument list an
00:36:16.400 array let's just save them off as an instance variable for now and again we got to return
00:36:21.599 self to make the chaining work running the test again the next error is an argument error
00:36:28.160 wrong number of arguments one for zero the quintessential very confusing ruby error message that basically means
00:36:34.800 the the method was invoked with one argument but ruby only had its arity at
00:36:39.920 zero it only expected it to have zero arguments and that happened on line 52 so let's go to line 52.
00:36:49.119 that's where we define our singleton method of course when you use define method with a block the method is
00:36:54.480 defined with the same number of arguments as the block and the block takes a take zero
00:36:59.599 arguments so let's change that because now we have methods that take in arguments let's have the block take in
00:37:05.760 the splat arguments as well running the test again we now get the
00:37:11.760 failure and failure is good failure is better than error you expected true but got false on line
00:37:17.680 61 of the tests so let's switch back over to the test and look at this this is where we try to
00:37:23.280 call warehouse.include1234 and expect it to be true except it's false we can kind of intuit
00:37:29.599 that what's happening is we're not doing any matching on the argument list right so just the last stub is winning
00:37:34.960 no matter what the arguments are and the last stub says return false so we could actually invoke include with
00:37:40.960 any arguments it would always be false because that's just the last one because when we call stub you remember
00:37:47.040 we just define the singleton method to invoke the last definition that we got so this clearly needs to
00:37:52.560 change it needs to be more of like search for the right definition
00:37:58.880 how do we search for the right definition well let's loop through the list of definitions
00:38:04.400 we also have to do this in reverse order just to make sure that we get the last stub
00:38:09.440 if they're stubbed for the exact same argument list the last one should win again this should be covered by tests
00:38:14.640 but i want to show you so many things that i don't have time for it so let's loop through them in reverse order and detect
00:38:20.800 the one that matches i don't exactly know how to match a definition yet so i'm just going to make up a method
00:38:26.560 that i will make exist later called matches question mark for each definition and i'll pass in the definition name the
00:38:34.000 message name and the list of arguments that were actually invoked for this specific definition call
00:38:44.720 and once we find the right one once we detect the right one actually detect is the same as find in ruby i'll
00:38:49.839 use fine because it's a little more clear we can just call it so reverse
00:38:55.359 through the definitions find the right one call it
00:39:02.000 this should work right oh my goodness what we've broken all of
00:39:07.599 the tests or at least many of them undefined method reverse for nil class
00:39:14.400 let's look at this again definitions is clearly defined right except it's not um in
00:39:22.400 a singleton method definition like this in a dynamic method definition self is changed and instance variables
00:39:28.960 are looked up on self so self here is actually obj the object
00:39:34.160 it's self in here is not the same as self out here it's pretty confusing but it actually i
00:39:39.200 mean it makes sense right like self should be the receiver of the calls
00:39:44.640 so this block is a closure it can take on its environment but not instance variables
00:39:50.160 one easy fix is to just use a local variable and define a local variable outside the block to be the instance variable
00:39:56.320 cc definitions so now this block can access the local variable
00:40:02.720 all right running the tests the next thing is that there's undefined method matches question mark
00:40:08.640 because we haven't defined that for expectation definition expectation and definitions don't know how to match themselves yet
00:40:14.400 so let's fix that
00:40:20.240 what does it mean to match as an expectation definition given a message and an argument list well the message should match
00:40:28.480 the message name it was defined with and we should either we will either match if
00:40:33.680 we weren't defined with any arguments at all if arguments is just nil or the list of
00:40:39.359 arguments matches the list of arguments that were passed in
00:40:44.880 this looks a lot like i'm defining some sort of equality operator here but of course equal equal can't really take
00:40:50.960 multiple arguments at least not and have it look cleanly so and matches also is a stronger name so i
00:40:56.720 like it better okay let's run the test and we have six passing tests we have
00:41:02.400 mocks and stubs with arguments in the right order so i'm pretty psyched
00:41:15.839 so our library is pretty rocking um there are some caveats though that i do want to address because i'm
00:41:21.280 sure there's some advanced drivers out there that are like what's what's going on um if you call with for example
00:41:27.200 and uh pass arguments that were not expected at all you'll get something like a no method
00:41:32.640 error because the detect will return nil and then you'll try to call call on nil
00:41:38.319 our spec for example just raises a separate error that says hey you call this stub in a way that wasn't we don't know how to to fix that so that
00:41:45.839 wouldn't be hard to add you could add some sort of like null callable thing that detect return
00:41:50.960 by default and then that gets called and just invokes an error but i'll leave that to you guys define
00:41:57.359 singleton method and singleton class are object methods so our object library would kind of blow up if you tried to stub something on
00:42:03.280 basic object this is not incredibly difficult to fix but it makes the code a lot uglier because you have to use kind of more
00:42:09.359 syntactic ruby meth or syntax ruby syntax to get at things like the
00:42:14.720 singleton class so it just makes things look a little uglier so i didn't go down that road
00:42:20.560 method define the method defined question mark method returns false on private methods as it probably should
00:42:25.599 but that also means that if you try to stub a private method why are you trying to stub a private method but if you try to do people people do
00:42:32.319 this we would we would not restore it correctly so i'll leave that up to astute readers to figure out
00:42:38.640 and then of course we would want to integrate reset automatically probably at the end of test that's not that hard to do in both mini
00:42:44.640 test and rspec in many tests you hook into tear down we define a module that defined tear
00:42:50.480 down make sure to call super if you do that and then in our spec you would use an
00:42:55.599 after hook our library is ruby 193 and above only as far as i can tell
00:43:00.960 it's a really big pain in the butt to uh maintain a mock object library on something before that there's just weird
00:43:07.680 behavior and things so i didn't even try to go down that road but even given these things i think our
00:43:14.000 library is pretty cool and it's pretty cool that we were able to build it in ruby in about 30 minutes
00:43:19.359 it's very likely that allow and expect will make their way into our spec to be kind of a new dsl for doing our spec
00:43:25.440 mocks and so look out for that follow the issue on github if you're interested i will push
00:43:31.839 this to github soon please don't use it it's but if you do want to hack on it to understand mocking
00:43:37.760 more if you want to write some additional tests and try to figure out how to make ancient mock do the right thing for you feel free and
00:43:44.720 there's a link to the slides i'll also tweet them shortly via my twitter account a lindemann thank
00:43:50.640 you so much i hope you learned some things and i had a great time