Search code examples
rspeccontrollersscaffold

RSpec Scaffold Controller, understanding the defaults being given


I'm working through an rspec tutorial (peepcode tutorial). I generated some scaffold and I was hoping someone can help explain how the describe could be re-written to read a bit clearer for newbie.

describe "POST create" do

    describe "with valid params" do
      it "assigns a newly created weather as @weather" do
        Weather.stub(:new).with({'these' => 'params'}) { mock_weather(:save => true) }
        post :create, :weather => {'these' => 'params'}
        assigns(:weather).should be(mock_weather)
      end

end

This line of code is what I'm trying to understand is this one

Weather.stub(:new).with({'these' => 'params'}) { mock_weather(:save => true) }

I have never seen a method being put inside braces. What does this actually mean?

{ mock_weather(:save => true) }

Solution

  • As for your first question about "how to make it a bit clearer", we could begin by not using mocks.

    A mock object is useful when you can not depend on a predictable behavior of a secondary object, an object that is not important but must be present in your test case. Typical examples of the usage of mock objects are database queries, network usage, file i/o. You don't want your tests to fail because your computer lost network connection, or a database is not available.

    The generated code that you got from the rails g scaffold is not optimal. The generated code was generated in a way that will make all tests pass, and for that it uses mock objects. I don't know why they do that, I think a better default would be failing tests so that you actually need to do something to make them pass.

    I would delete the generated mocks and do something like the following:

    #spec/controllers/weather_controller_spec.rb
    describe "POST create" do
      describe "with valid params" do
        it "assigns a newly created weather as @weather" do
          post :create, :weather => {'location' => 'ORD', 'temp'=>'35', 'sample_time'=>'2011-02-04T20:00-0500'}
          assigns(:weather).should be_valid
        end
    
        it "should redirect you to the weather show page" do
          post :create, :weather => {'location' => 'ORD', 'temp'=>'35', 'sample_time'=>'2011-02-04T20:00-0500'}
          response.should redirect_to(weather_path(assigns[:weather]))
        end
      end
    
      describe "without valid params" do
        it "should notify that a location is required" do
          post :create, :weather => {'temp'=>'35', 'sample_time'=>'2011-02-04T20:00-0500'}
          flash[:notice].should == 'Location is required.'
          assigns(:weather).should_not be_valid
        end
    
        it "should notify that a temperature is required" do
          post :create, :weather => {'location' => 'ORD', 'sample_time'=>'2011-02-04T20:00-0500'}
          flash[:notice].should == 'A temperature is required.'
          assigns(:weather).should_not be_valid
        end
    
        it "should notify that a sample time is required" do
          post :create, :weather => {'location' => 'ORD', 'temp'=>'35'}
          flash[:notice].should == 'A sample time is required.'
          assigns(:weather).should_not be_valid
        end
      end
    end
    

    Notice that we are not using mock objects, and so the code is reduced to making the POST call with some parameters and verifying that object is valid. Since will have to write the validation rules in your model file, and you are not using a mock object, you can be sure that an actual validation is going on.

    With the auto generated mock files you don't have to do anything at all and the tests will continue to pass forever. That is a bad idea, and bad practice.

    Also notice that you should write more tests that exercise the cases where there are invalid or missing parameters. And again, by doing assigns(:weather).should_not be_valid you are verifying that your validations are doing their job.

    Writing the dictionary of parameters every time you call post :create is repetitive, fragile and ugly. You should study how to use fixtures. For example, with Factory Girl

    #spec/factories.rb
    Factory.define :weather_valid do |f|
      f.location "ORD"
      f.temp "35"
      f.sample_time "2011-02-04T20:00-0500"
    end
    
    
    #spec/controllers/weather_controller_spec.rb
    describe "POST create" do
      describe "with valid params" do
        it "assigns a newly created weather as @weather" do
          post :create, :weather => Factory.build(:weather_valid).attributes
          assigns(:weather).should be_valid
        end
    
        it "should redirect you to the weather show page" do
          post :create, :weather => Factory.build(:weather_valid).attributes
          response.should redirect_to(weather_path(assigns[:weather]))
        end
      end
    ...
    

    That gives you reusable and more readable code.