Here is my Rspec when testing an API end point related to Users:
context "updating a user" do
let(:user) { User.create! }
it "should let me update a user without an email" do
put "/api/v1/users/#{user.id}", {:user => {:first_name => 'Willy'}}.to_json, {'CONTENT_TYPE' => 'application/json', 'HTTP_AUTHORIZATION' => "Token token=\"#{auth_token.access_token}\""}
p user.inspect
end
And the controller action that I am testing looks like this:
def update
begin
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
p #user.inspect
render json: #user, :except => [:created_at, :updated_at]
else
render json: { :errors => #user.errors }, :status => :unprocessable_entity
end
rescue ActiveRecord::RecordNotFound
head :not_found
end
end
Surprisingly, the #user.inspect in the controller shows this:
"#<User id: 2, first_name: \"Willy\", last_name: nil, email: nil, state: nil, created_at: \"2013-06-22 11:21:22\", updated_at: \"2013-06-22 11:21:22\">"
And the user.inspect in the rspec, right after the call to the controller has been done, looks like this:
"#<User id: 2, first_name: nil, last_name: nil, email: nil, state: nil, created_at: \"2013-06-22 11:21:22\", updated_at: \"2013-06-22 11:21:22\">"
Why does the Rspec not catch the updates? I mean, I have tested this manually and the database gets updated correctly.
What am I missing here?
In rspec example you define user method with let, which returns ActiveRecord object. Your controller is creating different object, that points to the same database entry. Change in db is not reflected in user object in rspec example, as there is no callback mechanism that would notify it to change.
Using #reload method on AR object in test should solve your problem, as it forces reloading data from db.
Related
I am trying to write a simple test to validate updating an Employee and while it works in practice I wanted to write the test anyway.
RSpec.describe EmployeesController, type: :controller do
before(:each) do
admin_user = FactoryBot.create(
:user,
user_type: 1,
email: "admin#admin.com",
password: "oeifhoi2345tf",
password_confirmation: "oeifhoi2345tf"
)
login_as(admin_user)
#employee = create(:employee)
end
it 'expects employee values to update following update' do
p #employee
put :update, params: {
id: #employee[:id],
employee: { name: "New Name" }
}
#employee.reload
p #employee
expect(#employee.name).to eq("New Name")
end
end
The test fails and #employee.name remains unchanged. I have a feeling the update it not even occurring because I added a print line to my controller and I do not see it in my logs:
def update
p "IN UPDATE"
if #employee.update(employee_params)
redirect_to edit_employee_path(#employee[:id])
else
render :edit
end
end
Is there something I am missing in my put call?
I must be doing something wrong or I need glasses. I am following this tutorial here:
http://vaidehijoshi.github.io/blog/2015/09/29/using-pundit-the-cool-kid-of-authorization/
I've created the application_policy.rb file and user_policy.rb files inside the app/policies folder as instructed.
Pundit doesn't seem to detect my UserPolicy file inside IntegrationTests.
My Setup
Ruby on Rails 5 RC1
Ruby 2.2.4p230
Knock gem for JWT authentication
Pundit 1.1.0
user_policy.rb
class UserPolicy < ApplicationPolicy
def update?
user == resource
end
end
In my UserController, I have the defined REST API actions. Currently, I'm just testing the "update" action:
UsersController
def update
user = User.find_by({id: params[:id]})
authorize user
render json: { error: "Failed to find" }, status: :not_found and return unless user
if user.update!(user_params)
render json: user
else
render json: { error: "Not found" }, status: :not_found
end
end
users_controller_test.rb
test "update other user's data should raise NotAuthorized exception" do
#user_two = users(:two)
put user_path(#user_two.id), params: { first_name: "Jim" }, headers: #authorization_header
assert_response :success # forcing fail test for now to test plumbing
end
I am getting the following errors:
.E
Error:
UsersControllerTest#test_update_other_user's_data_should_raise_NotAuthorized_exception:
Pundit::NotDefinedError: unable to find policy `UserPolicy` for `#<User id: 298486374, first_name: "MyString", last_name: "MyString", email: "MyString", password_digest: "MyString", created_at: "2016-05-27 13:30:07", updated_at: "2016-05-27 13:30:07", role_id: nil>`
app/controllers/users_controller.rb:42:in `update'
test/controllers/users_controller_test.rb:60:in `block in <class:UsersControllerTest>'
Any ideas what I might be doing wrong?
Edit
If it's any help, my UserControllerTest file looks like this:
require 'test_helper'
class UsersControllerTest < ActionDispatch::IntegrationTest
def authenticate
token = Knock::AuthToken.new(payload: { sub: users(:one).id }).token
#authorization_header = { HTTP_AUTHORIZATION: "Bearer #{token}" }
end
setup do
# load "#{Rails.root}/db/seeds.rb"
Rails.application.load_seed
authenticate
#user = users(:one)
end
test "logged in user should return ok" do
get users_path, headers: #authorization_header
assert_response :ok
end
test "not logged in user should return unauthorized" do
get users_path
assert_response :unauthorized
end
test "user json should not contain password_digest" do
get user_path(#user.id), headers: #authorization_header
assert_response :ok
json = JSON.parse(response.body)
assert json.key?("password_digest") == false
end
test "create user without authorization header should return created" do
user = {
first_name: "Bob",
last_name: "Brown",
email: "bob#gmail.com",
password: "abc",
password_confirmation: "abc"
}
post users_path, params: user
assert_response :created
json = JSON.parse(response.body)
assert !json.empty?
end
test "update user should return ok" do
put user_path(#user.id), params: { first_name: "Bob"}, headers: #authorization_header
assert_response :ok
updated_user = User.find_by({id: #user.id})
assert_equal "Bob", updated_user.first_name
end
test "update other user's data should raise NotAuthorized exception" do
#user_two = users(:two)
put user_path(#user_two.id), params: { first_name: "Jim" }, headers: #authorization_header
assert_response :success
end
test "delete user shoudl return ok" do
assert_difference "User.count", -1 do
delete user_path(#user.id), headers: #authorization_header
end
end
end
All my tests were passing before I added the Pundit stuff an hour ago.
To fix, I ran:
bin/spring stop
bin/spring binstub --remove --all
bundle update spring
bundle exec spring binstub --all
Okay....I don't know what the heck happened but apparently, I quit all my Mac terminal AND Atom IDE where I typed my code.
Then I opened my terminal and did a rails test command again, and this time it worked:
....E
Error:
UsersControllerTest#test_update_other_user's_data_should_raise_NotAuthorized_exception:
Pundit::NotAuthorizedError: not allowed to update? this #<User id: 298486374, first_name: "MyString", last_name: "MyString", email: "MyString", password_digest: "MyString", created_at: "2016-05-27 15:19:51", updated_at: "2016-05-27 15:19:51", role_id: nil>
app/controllers/users_controller.rb:42:in `update'
test/controllers/users_controller_test.rb:60:in `block in <class:UsersControllerTest>'
Bizarre...
TL;DR
Quit your IDE and Terminal
Reopen your Terminal, then try again
require 'spec_helper'
describe UsersController do
let(:user){ double(User, id: 2, name: "Jimbo", email: 'jimbo#email.com', password: 'passwordhuzzah', password_confirmation: 'passwordhuzzah') }
before do
mock_model("User")
end
describe 'PATCH #update' do
User.should_receive(:find).with(user.id.to_s).and_return user
user.should_receive(:update_attributes).with({ "email" => user.email, "name" => user.name, "password" => user.password, "password_confirmation" => user.password_confirmation })#.and_return true
patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }
flash[:error].should == "could not update user"
response.status.should == 200
end
end
codebase:
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to #user, flash: { success: 'succesfully updated user' }
else
flash.now[:error] = "could not update user"
render 'edit'
end
end
While the above spec passes (and passes in just 0.05 seconds!) am I doing it correctly? With the mocks above the request, and the 'normal' expectations below it? It seems a bit clumsy. Not only is it hard to read, but if one expectation fails, all of them will appear to fail.
What makes me think I'm doing it wrong is a weird error I'm getting. See the second line of the describe block, where I'm saying the instance of user (user) should have its update_attributes triggered with updated attributes? Note the and_return true method I've commented out. When it's chained on, running the above spec hits me with this stinker:
1) UsersController should_receive
Failure/Error: patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }
NoMethodError:
undefined method `model_name' for RSpec::Mocks::Mock:Class
While this a over my head, I think it's because the user 'instance' isn't actually an instance, just a hash. I thought because I used mock_model and the double inherits from that mocked model, all of the active-record-y stuff such as the 'model_name' method would be set.
Anyway, how should I write this spec properly? I don't want to use FactoryGirl, I want to keep it all contained so my specs are fast and accurately report a fault.
Yes, in this case of controller testing, your basic flow is "normal", except that your innermost code needs to be within an it block (and perhaps is and is just not transcribed properly).
However, you're not making use of the mock_model call, since you're not doing anything with the result. This method doesn't make any fundamental changes to the class whose string name you pass it, it simply creates a mock object that simulates an ActiveRecord instance, which in your case you effectively discard. Like all RSpec doubles, the first parameter is just giving it a name that can be used for error messages and such.
So yes, the reason you're getting the error when you return true is that redirect_to expects #user to be an ActiveRecord instance and while it's not just a Hash as you suggest it might be, it does not have the model_name method it needs.
There are lots of ways to rewrite this, particularly if you want to support both the success and failure cases, but one way that minimizes the changes is (not fully tested):
describe UsersController do
let(:user){ mock_model(User, id: 2, name: "Jimbo", email: 'jimbo#email.com', password: 'passwordhuzzah', password_confirmation: 'passwordhuzzah') }
describe 'PATCH #update' do
it "should fail in this case" do
User.should_receive(:find).with(user.id.to_s).and_return user
user.should_receive(:update_attributes).with({ "email" => user.email, "name" => user.name, "password" => user.password, "password_confirmation" => user.password_confirmation })#.and_return true
patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }
flash[:error].should == "could not update user"
response.status.should == 200
end
end
end
So it seems that I must be doing this wrong.
Task.create :name => 'apples'
(0.2ms) begin transaction
(0.2ms) rollback transaction
=> #<Task id: nil, name: "apples", task: nil, created_at: nil, updated_at: nil>
Then I thought, maybe my controller is wrong:
def create
Task.create(params[:task])
redirect_to tasks_path, :flash => {:success => 'We have created the task.'}
end
because it seems that my tests, using capybara, are failing - because they can't create a task.....
thoughts?
You can't save a Rails model to the database if it has a validation which fails, or a before_save callback which returns false
I'm relatively new to programming, Rails, Ruby, Rspec, and the like, so thanks for your help!
My specs were very repetitive, so I wrote some spec helper methods. I can't figure out how to properly use them in my specs. Specifically, I have a users controller with create:
def create
#user = User.new(params[:user])
if #user.save
redirect_to user_path(#user)
else
render :action => :new
end
end
A bit in the spec helper that creates a valid user:
def valid_user_eilif
#test_image = Rails.root + "spec/fixtures/images/seagull.jpg"
#file = Rack::Test::UploadedFile.new(#test_image, "image/jpeg")
user = User.create!(:username => "eilif", :email => "eilif#email.org",
:image => #file, :bio => "Lots of text that I don't want to write",
:signature_quote => "Yet more text.")
user.save!
user
end
And then in my user controller spec:
before (:each) do
post :create, :user => valid_user_eilif
end
it 'should assign user to #user' do
assigns(:user).should eq(User.last)
end
When I run the spec I get the error:
Failure/Error: assigns(:user).should eq(User.last)
expected #<User id: 1, username: "eilif", email: "eilif#email.org", bio: "Lots of text that I don't want to write", signature_quote: "I feel empty.", image_file_name: "seagull.jpg", image_content_type: "image/jpeg", image_file_size: 10475, image_updated_at: "2011-05-10 23:35:55", created_at: "2011-05-10 23:35:56", updated_at: "2011-05-10 23:35:56">
got #<User id: nil, username: nil, email: nil, bio: nil, signature_quote: nil, image_file_name: nil, image_content_type: nil, image_file_size: nil, image_updated_at: nil, created_at: nil, updated_at: nil>
So, I assume I'm incorrectly posting to create, since nothing is created? What's the proper way to do this?
Ideally controller specs shouldn't depend on the model being able to create a row in the database. With such a simple action you can mock out the dependencies:
describe UsersController do
context "on success" do
before(:each) do
#user = mock_model(User,:save=>true)
User.stub(:new) {#user}
post :create, :user => {}
end
it "redirects" do
response.should redirect_to(user_path(#user))
end
it "assigns" do
assigns[:user].should == #user
end
end
context "on failure" do
it "renders 'new'" do
#user = mock_model(User,:save=>false)
User.stub(:new) {#user}
post :create, :user => {}
response.should render_template "users/new"
end
end
end
Notice that the specs don't pass anything in params[:user]. This helps enforce the MVC separation of concerns, whereby the model is responsible for handling the attributes, ie. validating, setting up associations, etc. You can't always keep controllers this 'skinny', but it's a good idea to try.
It looks like the problem is that #user doesn't get refreshed after the save. Try assigns(:user).reload.should eql(User.last).
But there's another slight problem, and that's probably still going to fail. You shouldn't be calling post with :user => valid_user_eilif; you want the attributes from your user record, not the actual user object itself. And you're essentially creating a new user in valid_user_eilif and then making your controller create that object again -- if you have any kind of unique constraints, you're going to get a conflict.
This is a good place to use something like factory_girl and mocks. For an example, take a look at how one of my projects handles controller specs. This example uses factory_girl, Mocha and shoulda. I'll annotate it with comments below:
describe MembersController, "POST create" do
before do
# Factory Girl - builds a record but doesn't save it
#resource = Factory.build(:member)
# Mocha expectation - overrides the default "new" behavior and makes it
# return our resource from above
Member.expects(:new).with({}).returns(#resource)
# Note how we expect it to be called with an empty hash; that's from the
# `:member` parameter to `post` below.
end
context "success" do
before do
post :create, :member => {}
end
# shoulda matchers - check for a flash message and a redirect
it { should set_the_flash.to(/successfully created/) }
it { should redirect_to(member_path(#resource)) }
end
context "failure" do
before do
# Mocha - To test a failing example in the controller, we override the
# default `save` behavior and make it return false, otherwise it would
# be true
#resource.expects(:save).returns(false)
post :create, :member => {}
end
# shoulda matchers - check for no flash message and re-render the form
it { should_not set_the_flash }
it { should render_template(:new) }
end
end