Question

I am in the process of freeing myself from FactoryGirl (at least in the lib folder). So, I start writing strange stuff like "mock" and "stub". Can somebody help a novice out?

I have this module

module LogWorker
   extend self
   def check_todo_on_log(log, done)
    if done == "1"
      log.todo.completed = true
      log.todo.save!
    elsif done.nil?
      log.todo.completed = false
      log.todo.save!
    end
  end 
end

log and todo are rails models with a todo :has_many logs association. But that should really not matter when working with stubs and mocks, right?

I have tried many things, but when I pass the mock to the method nothing happens,

describe LogWorker do
  it 'should check_todo_on_log'do
    todo = mock("todo")
    log = mock("log")
    log.stub!(:todo).and_return(todo)
    todo.stub!(:completed).and_return(false)
    LogWorker.check_todo_on_log(log,1)
    log.todo.completed.should eq true
  end
end

Failures:

  1) LogWorker should check_todo_on_log
     Failure/Error: log.todo.completed.should eq true

       expected: true
            got: false

       (compared using ==

I would really like to see some spec that would test the LogWorker.check_todo_on_log method with stubs and/or mocks.

Was it helpful?

Solution

Firstly, your check_todo_on_log method is pretty bad. Never, ever use strings as options, especially when the string is "1". Also, if you pass "2", nothing happens. I'll assume though it is just a partial method, and your code isn't really like that :P

Looking at your code, you have three main problems. Firstly, you call LogWorker.check_todo_on_log(log,1). This won't do anything, as your method only does stuff when the second param is the string "1" or nil. Secondly, you stub todo.completed so it always returns false: todo.stub!(:completed).and_return(false). You then test if it is true. Obviously this is going to fail. Finally, you don't mock the save! method. I don't know how the code is actually running for you (it doesn't work for me).

Below is how I would write your specs (note that they are testing weird behaviour as the check_todo_on_log method is also strange).

Firstly, there is an easier way to add mock methods to a mock object. You can pass keys and values to the mock methods, and they will automatically be created.

Next, I put the mocks into let blocks. This allows them to be recreated easily for each test. Finally, I add a test for each possible behaviour of the function.

# you won't need these two lines, they just let the code be run by itself
# without a rails app behind it. This is one of the powers of mocks, 
# the Todo and Log classes aren't even defined anywhere, yet I can 
# still test the `LogWorker` class!
require 'rspec'
require 'rspec/mocks/standalone'

module LogWorker
   extend self
   def check_todo_on_log(log, done)
    if done == "1"
      log.todo.completed = true
      log.todo.save!
    elsif done.nil?
      log.todo.completed = false
      log.todo.save!
    end
  end
end

describe LogWorker do
  let(:todo) { mock("Todo", save!: true) }
  let(:log) { mock("Log", todo: todo) }
  describe :check_todo_on_log do
    it 'checks todo when done is "1"'do
      todo.should_receive(:completed=).with(true)
      LogWorker.check_todo_on_log(log,"1")
    end
    it 'unchecks todo when done is nil'do
      todo.should_receive(:completed=).with(false)
      LogWorker.check_todo_on_log(log,nil)
    end

    it "doesn't do anything when done is not '1' or nil" do
      todo.should_not_receive(:completed=)
      LogWorker.check_todo_on_log(log,3)
    end
  end
end

Notice how I am using behaviour based testing? I'm not testing that an attribute on the mock has a value, I am checking that an appropriate methods are called on it. This is the key to correctly using mocks.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top