enspiral/loomio — pull request #22 by craigambrose (2012-03-13 09:40:24+00:00)
Title: Some examples of using mocks/doubles

Jon asked for some mocking examples, so here you go. I've refactored all the tests for MotionsController to use mocks. No real DB access for that spec file anymore.

I'll draw your attention to a few points. First up, unrelated to mocking, I'm using "let" in preference to assigning instance variables in before blocks. For example:

```
let(:group) { stub_model(Group) }
```
This means that there is a method called group, available to all the specs in the context, which will execute that block the first time it's called. So, simple lazy evaluation. I find that this lets you do more shared setup for your specs, without having to worry that one or two specs wont actually use those models.

Next up, lets say we want to declare a mock object in place of a group, we've got a few different options here. First up, go read this:

http://rubydoc.info/gems/rspec-mocks/file/README.md

The rspec-mocks library gives us this method:

```
double("group")
```

That's often the tools of choice. Notice how it uses a string "group", which is just a string used to identify this object in test error message and stuff. By using a string it means that this test file doesn't even need to load the Group class, so we're totally decoupled. It's called "double" because this object is a stand-in for a real group, like a stunt double.

The rspec-rails gem also provides a couple of extra helpers:

```
stub_model(Group)
mock_model(Group)
```

These two do need to know about the Group class, and they basically create a double but also provide a couple of methods that ActiveRecord objects support, like :id, :new_record? etc. The id method will return a random integer (the same each time for a given object though). I used this heavily in my refactoring so that I could use that id to pass into controller actions.

The only difference between stub_model and mock_model is that the mock_model will raise an exception if you call a method on it that you didn't setup an expectation on, the stub_model will silently return nil in such cases.

You'll notice that my tests look quite different to what was there before. Testing with doubles is not at all the same.

Previously, the tests would frequently build a model in the DB, run the controller action, and check that the model looked like (had the attribute values of) the correct things had been done to it. Ie, if we wanted to test that close_voting! had been called, we would check to see if motion.phase == 'closed'.

Now, we don't do that, instead, I say motion.should_receive(:close_voting!), and I do that before the call to the controller action. I don't check that the method worked. I can't, I'm not even using a real motion object. Motion isn't the class under test here, MotionsController is. The specs for the Motion class will check that close_voting! actually works.

Also note that in this refactoring, I found an error. The spec "cannot close a motion" on line 30 never actually worked. The previous code checked that @motion.phase.should == 'voting' but if it had done a reload on that model, it would have found that the phase had changed to "closed" in the DB.

This is because nothing in this controller checks to see if the user is an admin. That's why my two contexts on lines 14 and 25 actually have the same setup block. They were the same before as well. So, I've marked that failing test as pending for now, but it does need fixing.