├── README.md └── spec └── controller_spec.rb /README.md: -------------------------------------------------------------------------------- 1 | #A template for Rails Rspec controller testing 2 | --- 3 | 4 | We have used a generic "ItemController" and a subsequent "Item" model, with 'item' objects. Update your controller test to include the appropriate model/controller names. 5 | 6 | --- 7 | ##\#New Action 8 | This tests the #new action in the controller, we provide some examples for tests, both hitting the database and not. 9 | 10 | ###Context: with hitting the database 11 | ```ruby 12 | before(:each) do 13 | get :new 14 | end 15 | 16 | it "creates a new item" do 17 | expect(assigns(:item)).to be_a_new(Item) 18 | end 19 | ``` 20 | 21 | ###Context: without hitting the database 22 | ```ruby 23 | let(:item_double) { double("item_double")} 24 | before(:each) do 25 | Item.stub(:new).and_return(item_double) 26 | get :new 27 | end 28 | 29 | it "creates a new item" do 30 | expect(assigns(:item)).to be(item_double) 31 | end 32 | ``` 33 | --- 34 | ##\#Create Action 35 | This tests the #create action in the controller, we provide some examples for tests, both hitting the database and not. 36 | 37 | ###Context: with hitting the database 38 | ```ruby 39 | it "creates a new Item" do 40 | expect{ 41 | post :create, item: FactoryGirl.attributes_for(:item) 42 | }.to change(Item,:count).by(1) 43 | end 44 | ``` 45 | ###Context: without hitting the database 46 | ```ruby 47 | let(:item_double) { double("item_double")} 48 | before(:each) do 49 | Item.stub(:new).and_return(item_double) 50 | item_double.stub(:save).and_return(true) 51 | end 52 | 53 | it "creates a new item" do 54 | post :create 55 | expect(assigns(:item)).to be(item_double) 56 | end 57 | 58 | it "redirects to the correct url" do 59 | post :create 60 | expect(response).to redirect_to root_url 61 | end 62 | ``` 63 | --- 64 | ##\#Edit Action 65 | This tests the #edit action in the controller, we provide some examples for tests, both hitting the database and not. 66 | 67 | ###Context: with hitting the database 68 | ```ruby 69 | let(:item) {Item.create(first_attribute: "My name", second_attribute: 23)} 70 | 71 | before(:each) do 72 | get :edit, id: item 73 | end 74 | 75 | it "finds a specific item" do 76 | expect(assigns(:item)).to eq(item) 77 | end 78 | ``` 79 | ###Context: without hitting the database 80 | ```ruby 81 | let(:item) {double("item")} 82 | 83 | it "finds a specific item" do 84 | Todo.should_recieve(:find).once.and_return(item) 85 | get :edit, id: item 86 | end 87 | ``` 88 | --- 89 | ##\#Update Action 90 | This tests the #update action in the controller, we provide some examples for tests, both hitting the database and not. 91 | 92 | ###Context: with hitting the database 93 | ```ruby 94 | let(:item) {Item.create(first_attribute: "My name", second_attribute: 23)} 95 | 96 | it "updates an item with valid params" do 97 | post :update, id: item, item: {first_attribute: "Updated name", second_attribute: 23} 98 | item.reload 99 | expect(item.first_attribute).to eq("Updated name") 100 | end 101 | ``` 102 | ###Context: without hitting the database 103 | ```ruby 104 | let(:item) {double("todo")} 105 | let(:attrs) {first_attribute: "Updated name", second_attribute: 23} 106 | 107 | it "updates an item with valid params" do 108 | Item.stub(:find).and_return(item) 109 | item.should_recieve(:update_attributes).with(attrs.stringify_keys) 110 | post :update, id: item, item: attrs 111 | end 112 | 113 | it "redirects to item once updated" do 114 | item = stub_model(Item) 115 | Item.stub(:find).and_return(item) 116 | item.stub(:update_attributes).and_return(true) 117 | post :update, id: item, item: attrs 118 | expect(response).to redirect_to(item) 119 | end 120 | ``` 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /spec/controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | #Change "ItemController" to the appropriate name 4 | describe ItemController do 5 | 6 | #--------------------------------------# 7 | # The #new action # 8 | #--------------------------------------# 9 | describe "The #new action" do 10 | context "hitting the database" do 11 | # In this context, we are writing tests that will hit the 12 | # database. 13 | 14 | # Factoring out the GET :new HTTP request before each test 15 | # The #new action might not need many tests to begin with 16 | before(:each) do 17 | get :new 18 | end 19 | 20 | # The #new action should create a new object, item, usually a new 21 | # instance of a model class "Item" 22 | it "creates a new item" do 23 | expect(assigns(:item)).to be_a_new(Item) 24 | # The assigns(:item) is calling the instance variable, 25 | # @item in your controller. The test then asks, is it 26 | # an instance of the 'Item' class. 27 | end 28 | end 29 | 30 | context "Not hitting the database" do 31 | # In this context, we are writing tests that will NOT hit 32 | # the database. This can help test speeds, and remove coupling 33 | # with the model/db. 34 | let(:item_double) { double("item_double")} 35 | # creating a double 36 | 37 | before(:each) do 38 | allow(Item).to receive(:new).and_return(item_double) 39 | # When the .new method is called on the Item class, return 40 | # the item_double. This stops the creation of a new instance 41 | # which stops the test hitting the database. 42 | get :new 43 | end 44 | 45 | it "creates a new item" do 46 | expect(assigns(:item)).to be(item_double) 47 | #expect the instance of item to be a item_double. 48 | end 49 | end 50 | end 51 | 52 | #--------------------------------------# 53 | # The #create action # 54 | #--------------------------------------# 55 | describe "The #create action" do 56 | context "Valid params" do 57 | context "hitting the database" do 58 | it "creates a new Item" do 59 | expect{ 60 | # expect can be passed a block 61 | post :create, item: FactoryGirl.attributes_for(:item) 62 | # here, you would need an item factory using the FactoryGirl 63 | # gem. This assigns the item: instance the attributes for an 64 | # item. 65 | }.to change(Item,:count).by(1) 66 | # The rest of the test says the item model should have one more 67 | # instance 68 | end 69 | 70 | it "redirects to the correct url" do 71 | #in this example we will test the root path 72 | post :create, item: FactoryGirl.attributes_for(:item) 73 | expect(response).to redirect_to root_url 74 | end 75 | end 76 | 77 | context "not hitting the database" do 78 | let(:item_double) { double("item_double")} 79 | before(:each) do 80 | allow(Item).to receive(:new).and_return(item_double) 81 | allow(item_double).to receive(:save).and_return(true) 82 | # we need to stub the #new and the #save methods on the 83 | # class and double 84 | end 85 | 86 | it "creates a new item" do 87 | post :create 88 | #expect the instance of item to be a item_double. 89 | expect(assigns(:item)).to be(item_double) 90 | end 91 | 92 | it "redirects to the correct url" do 93 | #in this example we will test the root path 94 | post :create 95 | expect(response).to redirect_to root_url 96 | end 97 | end 98 | end 99 | end 100 | 101 | #--------------------------------------# 102 | # The #edit action # 103 | #--------------------------------------# 104 | describe "#edit" do 105 | 106 | context "hitting the database" do 107 | #this sets up an insatnce of "Item" to be used in our tests as "item" 108 | #change "Item" and "item" as appropriate 109 | #Also change attributes as needed or swap out for a Factory 110 | let(:item) {Item.create(first_attribute: "My name", second_attribute: 23)} 111 | 112 | #this calls the #edit action before each test 113 | before(:each) do 114 | get :edit, id: item 115 | end 116 | 117 | #change "item" to appropriate instance 118 | it "finds a specific item" do 119 | expect(assigns(:item)).to eq(item) 120 | end 121 | 122 | #there should be nothing to change here unless you render a view other than edit 123 | it "renders the edit view" do 124 | expect(response).to render_template("edit") 125 | end 126 | end 127 | 128 | context "not hitting the database using mocks and doubles" do 129 | 130 | #this creates a double for item that can be used for further tests 131 | let(:item) {double("item")} 132 | 133 | #uses mocks to test that find was called and the correct id is passed 134 | #change "Todo" to the name of your Class 135 | it "finds a specific item" do 136 | Todo.should_recieve(:find).once.and_return(item) 137 | get :edit, id: item 138 | end 139 | 140 | #there should be nothing to change here unless you render a view other than edit 141 | it "renders the edit view" do 142 | expect(response).to render_template("edit") 143 | end 144 | end 145 | end 146 | 147 | #--------------------------------------# 148 | # The #update action # 149 | #--------------------------------------# 150 | describe "#update" do 151 | 152 | context "hitting the database" do 153 | 154 | #this sets up an insatnce of "Item" to be used in our tests as "item" 155 | #change "Item" and "item" as appropriate 156 | #Also change attributes as needed or swap out for a Factory 157 | let(:item) {Item.create(first_attribute: "My name", second_attribute: 23)} 158 | 159 | 160 | it "updates an item with valid params" do 161 | #this sends a post request to the #update item 162 | #it also passes the params it needs ("id" & "item") with the update 163 | post :update, id: item, item: {first_attribute: "Updated name", second_attribute: 23} 164 | item.reload 165 | expect(item.first_attribute).to eq("Updated name") 166 | end 167 | 168 | it "redirects to item once updated" do 169 | #update "item" throughout as needed 170 | #sending item: can be done with a factory and one explicit change 171 | post :update, id: item, item: {first_attribute: "Updated name", second_attribute: 23} 172 | #change redirect as desired 173 | expect(response).to redirect_to(item) 174 | end 175 | 176 | #this test assumes that validates :first_attribute, presence: true is in Item model 177 | #is redundant if no params validations 178 | it "renders edit if params are invalid" do 179 | post :update, id: item, item: {first_attribute: nil, second_attribute: 23} 180 | expect(response).to render_template("edit") 181 | end 182 | end 183 | 184 | context "not hitting the database" do 185 | 186 | #this creates a double to stand in place of @item in controller 187 | let(:todo) {double("todo")} 188 | let(:attrs) { { first_attribute: "Updated name", second_attribute: 23} } 189 | 190 | it "updates an item with valid params" do 191 | #we define the attrs that we will send through to be updated 192 | 193 | allow(Item).to receive(:find).and_return(item) 194 | #declares the method to be called in controller and what it's called with (attrs) 195 | item.should_recieve(:update_attributes).with(attrs.stringify_keys) 196 | post :update, id: item, item: attrs 197 | end 198 | 199 | it "redirects to item once updated" do 200 | #change "item" and Item as required 201 | item = stub_model(Item) 202 | #stubs out find and returns stub_model 203 | allow(Item).to receive(:find).and_return(item) 204 | allow(item).to receive(:update_attributes).and_return(true) 205 | post :update, id: item, item: attrs 206 | #change redirect as desired 207 | expect(response).to redirect_to(item) 208 | end 209 | 210 | it "renders edit if params are invalid" do 211 | #creates a double and defines the behaviour expected from update_attributes method 212 | item = double("item", update_attributes: false) 213 | allow(Item).to receive(:find).and_return(item) 214 | post :update, id: 1, item: item 215 | #change view to render as required 216 | expect(response).to render_template("edit") 217 | end 218 | end 219 | end 220 | end 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | --------------------------------------------------------------------------------