Spec RSpec model attribute setter
I'm using Sinatra (1.2) and RSpec (2.5) and would like to create a new object with an attribute TDD style. This is how the end result should look like:
class User
def initialize(name)
@name = name
end
end
I know I have to write the example before the implementation but I'm trying to explain my question here. :) Here is the not working spec I have so far:
describe User
it "creates a new user object" do
name = mock("A name")
user = mock(User) # shouldn't do this, see the reply's
user.should_receive(:name=).with(name)
User.new(name)
end
end
When I run RSpec I get the "expected: 1 time, received 0 times" error. Any idea how I can explain RSpec I would like to assign the name attribute?
Note: I'm not using Rails, n开发者_Go百科ot using ActiveRecord or anything, just Ruby.
First of all, let me explain why the spec you have written doesn't work:
You set an expectation that the mock object returned by mock(User)
should receive name=
. There are two problems with this. First of all the mock will receive nothing, because it is never called. mock(User)
returns a mock object, and it cannot be used to set expectations for what the User
class object will receive (to do that simply do User.should_receive(...)
). Secondly, even if you had set the expectation on the User
class object, that object will never receive name=
. There are two reasons for this, too: firstly because name=
(had it existed) would be an instance method, and as such not called on the class object, and secondly, you declare no name=
instance method. What your code does is that it sets an instance variable.
Now, how should you write a test for this? You shouldn't. Tests are to define and assert the behaviour, not the implementation. Setting an instance variable is pure implementation. There is in your example code no way to get the value of the @name
instance variable from outside of the class, therefore there is no reason to write a test for it.
Obviously your code is just an example, anything useful would do something with the @name
variable, and that is what you should test. Start by writing a test for what a User
object will be used for, then write all the implementation needed to fulfill that test (but no more). Write a test that reflects how the object will be used in actual production code.
I'd really recommend you don't approach this using mocks. It's not what they're for. In fact, specifying getters/setters like this is not really what TDD is for. The idea is to let a requirement drive the setters/getters into existence. For example, there might be a requirement that the user's name appear in a welcome message when he/she logs in. Then you might do something like this:
describe 'login process' do
it "displays user's name after successful login" do
user = User.new("Cimm", "cimm@somewhere.com", "secret")
post "/login", :email => "cimm@somewhere.com", :password => "secret"
last_response.body.should =~ /Welcome Cimm/m
end
end
This specifies behavior, and forces you to implement a means of setting the name attribute (via the constructor, in this case) and a means of accessing it. No need to specify the constructor directly.
Do you really want to mock the very object you are developing?
require 'rspec'
class User
attr_accessor :name
def initialize(name)
@name = name
end
end
describe User do
subject {User.new "other name"}
it "creates a new user object" do
subject.should respond_to :name=
end
end
精彩评论