It's been a while since the last time I wrote but better late than ever ;)
This time I'm going to dissert a little about the way we write our specs. In particular, as I am a rookie with rspec, I would like to know which is the right way to load data into our tests.
For that matter, I've found that there are mainly two approaches for that: 1) use fixtures or 2) use mocks/stubs instead.
Using Fixtures
Fixtures enable us to define data for our models in a human readable way using YAML syntax. A simple example, as you may have seen for Test:Unit, is shown below.
#spec/fixtures/user.yml
david:
id: 1
name: David Hasselhof
birthday: 1500-01-01
profession: Be-a-god
hashed_password: <%= EncryptionLibrary.encrypt("old_password") %>
Then, in your spec you only have to make reference to the fixture you want to load to make that happened. This approach is very handly for two reasons:
- This decouples the data to be tested with the specs (tests) itself.
- Is very simple to understand for small bunch of data.
- It works directly with the database which highly degrees the test's performance.
- It is hard to maintain, as it is used by all of your specs (for the description's block that invokes it), and for that: a single change may trigger many errors among different tests.
- It is very implementation dependant, it strongly relies on the model and the current specification of the database's schema.
#spec/views/chats/show.html.erb_spec.rb
describe "views/chats/show.html.erb" do
fixture :chats, :messages
before(:each) do
@chat = chats(:myChat)
render "/chats/show.html.erb"
end
it "should render attributes in paragraph" do
response.should have_text(/MyString/)
end
it "should render all messages for the chat" do
response.should have_tag("tr>td", "Hello!", 1)
response.should have_tag("tr>td", "How are you?", 1)
response.should have_tag("tr>td", "Excellent!", 1)
end
end
With these fixtures:
#spec/fixtures/chats.yaml
myChat:
name: MyString
id: 1
#spec/fixtures/messages.yaml
one:
data: Hello
chat_id: 1
version: 1
user_id: 1
two:
data: How are you?
chat_id: 1
version: 2
user_id: 1
three:
data: Excellent!
chat_id: 1
version: 3
user_id: 1
Mocks and stubs way
The other option is to use mocks and stubs. Although there is a lot of information in the web about it, to help our discussion, I will describe it very shortly. From my experience & research, I understand that the main difference between those two is the following:
Stubbing a method is all about replacing the method with code that returns a specified result (or perhaps raises a specified exception). Mocking a method is all about asserting that a method has been called (perhaps with particular parameters).
As you can see, stubbing lets you define methods that are not currently implemented or we decided not to depend on. On the other hand, as we mock objects we specify the behavior that we would expect on the mocked object, making our test more Behavior Driven Development.
This is the recommended way of using Rspec as it defines the way the objects must collaborate to accomplish a certain task.
Here we show how we implement the chat's example using mocks.
#spec/views/chat/show.html.erb_spec.rb
describe "/chats/show.html.erb" do
include ChatsHelper
before(:each) do
@chat = mock_model(Chat)
@chat.stub!(:name).and_return("MyString")
@chat.stub!(:id).and_return(1)
@chat.stub!(:to_param).and_return(1)
message_1 = mock_model(Message)
message_1.stub!(:data).and_return("Hello!")
message_1.stub!(:version).and_return(1)
message_2 = mock_model(Message)
message_2.stub!(:data).and_return("How are you?")
message_2.stub!(:version).and_return(2)
message_3 = mock_model(Message)
message_3.stub!(:data).and_return("Excellent!")
message_3.stub!(:version).and_return(3)
@messages = [message_1, message_2, message_3]
@chat.stub!(:messages).and_return(@messages)
@message.stub!(:data)
assigns[:chat] = @chat
render "/chats/show.html.erb"
end
it "should render attributes in paragraph" do
response.should have_text(/MyString/)
end
it "should render all messages for the chat" do
response.should have_tag("tr>td", "Hello!", 1)
response.should have_tag("tr>td", "How are you?", 1)
response.should have_tag("tr>td", "Excellent!", 1)
end
end
As you can see, the idea behind mocks is to define some behavior that should be part of certain task, and it is very human readable as it uses many features of Rspec mocks.
Conclusions
As we did with fixtures, we have to mention that Mocking has some drawbacks compared with fixtures, such as: it tights the tests with the data they need and forces us to write, perhaps, to much testing code.
Despite this, I prefer Mocks/Stubs because I think they provide a very elegant way to work with BDD. I think this way because I find in BDD a solid foundation to agile development, then I choose to prioritize it over the other alternatives. Please, make a comment debate this interesting subject that interests me so much ;)
4 comentarios:
You should have use "fixtures" instead of "fixture" to import the fixtures. At least for me, with rspec 2 and rails 3 worked that way.
Also the "yaml" fixture file extension didn't work for me on Rails 2.3.8. I managed to load the fixtures after renaming the files to '*.yml'
I’ll immediately grab your rss feed as I can’t find your e-mail subscription link or newsletter service. Do you have any? Please let me know in order that I could subscribe. Thanks.
I get the following error:
Mock 'User_1' received unexpected message :show_all_group with (no args)
I have searched through Google but none solution worked.
Publicar un comentario