Simple rspec question
I'm really struggling trying to learn rspec :( So I hope you can give me a little bit of help with a really simple create-action in the controller. I would like to use Rspec::mocks for this, as I think that is the way to do it? Instead of having to hit the database when testing.
I'm having a before_filter:
def find_project
@project= Project.find_by_id(params[:project_id])
end
The create action looks like this:
def create
@batch = Batch.new(params[:batch])
@batch.project = @project
if params[:tasks]
params[:tasks][:task_ids].each do |task_id|
@batch.tasks << Task.find(task_id)
end
end
if @batch.save
flash[:notice] = "Batch created successfully"
redirect_to project_batch_url(@project, @batch)
else
render :new
end
end
I'm really in doubt when it comes to @batch.project = @project
how do I def开发者_如何学Pythonine @project
? And also the whole params[:tasks][:task_ids].each
part.. Ya.. pretty much the whole thing :(
Sorry for this newbie question - Hope you guys can help or atleast point me in the right direction :)
Thanks
The idea of a controller spec is to check whether the actions are setting instance variables, and redirecting/rendering as needed. To set up the spec, you would normally create an object or a mock, set attributes/stubs, and then call the action, passing a params hash if necessary.
So for example (air code):
describe MyController do
before(:each) do
@project = mock_model(Project)
Project.stub(:find_by_id) {@project}
@batch = mock_model(Batch)
Batch.stub(:new) {@batch}
end
it "should redirect to project_batch_url on success" do
@batch.stub(:save) {true)
post :create, :batch => { :some_key => :some_value }, :tasks => { :task_ids => [1,2,3] }
response.should redirect_to(project_batch_url(@project,@batch))
end
it "should render :new on failure" do
@batch.stub(:save) {false)
post :create, :batch => { :some_key => :some_value }, :tasks => { :task_ids => [1,2,3] }
response.should render_template("new")
end
end
You can find lots more information about this in the RSpec Rails docs.
Using BDD helps you define your interfaces. So if your controller wants the project to create a batch and add some task id's, then "write the code you wish you had." In practice for controllers, this means trying to push logic out of the controller and into your models. Testing models tends to be more intuitive and are definitely faster than testing controllers.
Here are some possible specs (untested) from the "mockist" point of view:
# controller spec
describe BatchesController do
def mock_project(stubs={})
@mock_project ||= mock_model(Project, stubs)
end
def mock_batch(stubs={})
@mock_batch ||= mock_model(Batch, stubs)
end
context "POST create"
it "calls #create_batch_and_add_tasks on the project"
mock_project.should_receive(:create_batch_and_add_tasks).with(
:batch => { :name => 'FooBatch' },
:task_ids => [1,2,3,4]
)
Project.stub(:find).and_return(mock_project)
post :create, :batch => { :name => 'FooBatch' }, :tasks => { :task_ids => [1,2,3,4] }
# consider changing your params to :batch => { :name => 'FooBatch', :task_ids => [1,2,3,4] }
end
it "redirects to the project_batch_url on success" do
mock_project(:create_batch_and_add_tasks => mock_batch(:save => true))
Project.stub(:find) { mock_project }
post :create, :these_params => "don't matter because you've stubbed out the methods"
end
# controller
def create
@batch = @project.create_batch_and_add_tasks(
:batch => params[:batch],
:task_ids => params[:tasks].try([:tasks_ids])
)
if @batch.save
...
精彩评论