Reputation: 1398
I am writing a small Rails todo app for practice. I am trying to write my own Rspec tests for various things instead of just following a tutorial. I only have a few tests but a couple are failing and I can't figure out why. I try to make posting a question here a last resort but every time I have asked a question here I have gotten a good solution to my problem.
here's some code:
require 'spec_helper'
describe "TodosPages" do
describe "home page" do
before { visit root_path }
it "should have the content 'Todo App'" do
page.should have_content('Todo App')
end
it "should have title tag" do
page.should have_selector 'title'
end
it "should delete a todo and redirect to index" do
expect { click_link "Delete last todo" }.to change(Todo, :count).by(-1)
response.should redirect_to :action => 'index'
end
describe "Add Todo" do
subject { page }
before {click_button "Add todo"}
it { should have_selector('div.alert.alert-success', text: 'Todo Successfully Created') }
end
it "should have a error message" do
pending
end
it "should create a Todo" do
expect { click_button "Add todo" }.to change(Todo, :count).by(1)
end
end
end
todos_controller
class TodosController < ApplicationController
def index
@todo_items = Todo.all
@new_todo = Todo.new
render :index
end
def delete
@todo_delete = Todo.last
@todo_delete.delete
redirect_to :action => 'index'
end
def add
todo = Todo.create(:todo_item => params[:todo][:todo_item])
unless todo.valid?
flash[:error] = todo.errors.full_messages.join("<br>").html_safe
else
flash[:success] = "Todo Successfully Created"
end
redirect_to :action => 'index'
end
def complete
params[:todos_checkbox].each do |check|
todo_id = check
t = Todo.find_by_id(todo_id)
t.update_attribute(:completed, true)
end
redirect_to :action => 'index'
end
end
index.html.erb
<% @title = "Todo App" %>
<div class="container">
<div class="row">
<div class='span6' >
<h1 class="hero-unit">Simple Todo App</h1>
<p>All your todos here</p>
<%= form_for @new_todo, :url => { :action => "add" } do |f| %>
<%= f.text_field :todo_item %>
<%= f.submit "Add todo", class: "btn btn-primary" %>
<%end%>
<% if flash[:error] %>
<div class="alert alert-error">
<button type="button" class="close" data-dismiss="alert" >×</button>
<strong><%= flash[:error] %></strong>
</div>
<% end %>
<% if flash[:success] %>
<div class="alert alert-success">
<button type="button" class="close" data-dismiss="alert" >×</button>
<strong><%= flash[:success] %></strong>
</div>
<% end %>
<div class="well">
<%= form_tag("/todos/complete/", :method => "post") do %>
<ul style="list-style-type:none;">
<% @todo_items.each do |t| %>
<% if t.completed == true %>
<li style="color:grey;"> <%= check_box_tag "todos_checkbox[]",t.id %> <strike><%= t.todo_item %></strike> </li>
<% else %>
<li> <%= check_box_tag "todos_checkbox[]",t.id %> <%= t.todo_item %> </li>
<% end %>
<%end%>
</ul>
<%= submit_tag("Complete Todos", :class=>"btn btn-success") %>
<%end %>
</div> <!-- well -->
<%= link_to "Delete last todo", delete_path %>
</div> <!-- span6-->
</div> <!-- row -->
Failures:
1) TodosPages home page should create a Todo
Failure/Error: expect { click_button "Add todo" }.to change(Todo, :count).by(1)
count should have been changed by 1, but was changed by 0
# ./spec/views/index_page_spec.rb:29:in `block (3 levels) in <top (required)>'
2) TodosPages home page should delete a todo and redirect to index
Failure/Error: expect { click_link "Delete last todo" }.to change(Todo, :count).by(-1)
NoMethodError:
undefined method `delete' for nil:NilClass
# ./app/controllers/todos_controller.rb:11:in `delete'
# (eval):2:in `click_link'
# ./spec/views/index_page_spec.rb:14:in `block (4 levels) in <top (required)>'
# ./spec/views/index_page_spec.rb:14:in `block (3 levels) in <top (required)>'
3) TodosPages home page Add Todo
Failure/Error: it { should have_selector('div.alert.alert-success', text: 'Todo Successfully Created') }
expected css "div.alert.alert-success" with text "Todo Successfully Created" to return something
# ./spec/views/index_page_spec.rb:20:in `block (4 levels) in <top (required)>'
Finished in 0.33114 seconds
8 examples, 3 failures, 1 pending
Failed examples:
rspec ./spec/views/index_page_spec.rb:28 # TodosPages home page should create a Todo
rspec ./spec/views/index_page_spec.rb:13 # TodosPages home page should delete a todo and redirect to index
rspec ./spec/views/index_page_spec.rb:20 # TodosPages home page Add Todo
Randomized with seed 32260
Thanks in advance!
------Todo.rb
class Todo < ActiveRecord::Base
attr_accessible :todo_item, :completed
validates :todo_item, presence: true , length: { maximum: 25 }
end
Upvotes: 0
Views: 487
Reputation: 108
I'll take a stab at this. In no particular order:
Test 2 (TodosPages home page should delete a todo and redirect to index)
It looks like your Todo table is empty in your test database, so when you call Todo.last in your controller action, you get nil and then try and call delete on nil.
I can't see anywhere in your test where you are creating at least one Todo object before the delete test.
On a separate note, in your controller you may want to consider using the destroy instead of the delete method.
Test 1 (TodosPages home page should create a Todo)
What do you have in your model for Todo in terms of validations? Are there any required fields? I'm guessing that :todo_item is a required field, but in your rspec test you just click on the button and don't provide any input for that field so its probably failing the model validation.
In your controller action for add, use create! instead of create, and that should give you additional error messages if it is failing model validations.
Test 3 (TodosPages home page Add Todo )
May be failing for the same reason your above test is failing, i.e. the validations failing. Fix that first and then see if this test is passing.
Hope this helps.
Edit: Examples of tests passing params
My personal preference would be to separate controller tests (testing specific controller functionality) from integration type test (making sure pages are populated properly, buttons appear and are clickable etc). For the latter I like to use Cucumber. Then you could write some much easier Controller tests using Rspec, which allow you to test specific logic as well as pass params directly to the action you are testing. A test would look like
describe "add Todo" do
it "increases count by 1" do
expect {post: :add, todo_item: "string"}.to change(Todo, :count).by(1)
end
end
Alternatively if you want to continue doing your integration test in Rspec, (assuming you are using Capybara for simulating the browser), then you would need to give your todo_item text field a unique id or name, and you could do:
it "should create a Todo" do
fill_in "*todo_item_unique_id" with "some text"
expect { click_button "Add todo" }.to change(Todo, :count).by(1)
end
Upvotes: 1