I have an application running in rails 4.1 using mongoid as the orm. I created a model called User which has an attribute email. I am using RSpec for tests. I created the following spec
require 'spec_helper'
describe 'User' do
before(:each) do
#attr = {
user: {
email: "rahul#gmail.com"
}
}
end
it "should create a valid User instance" do
param = ActionController::Parameters.new(#attr)
param.require(:user).permit!
User.create!(param)
end
end
when I run the spec, I get the following error
Failure/Error: User.create!(param)
ActiveModel::ForbiddenAttributesError:
ActiveModel::ForbiddenAttributesError
I know this is related to strong parameters but couldn't figure out what I am doing wrong.
From the fine manual:
require(key)
[...] returns the parameter at the given key [...]
So saying param.require(:user) does nothing at all to param, it merely does an existence check and returns param[:user].
I think you want to say something more like this:
param = ActionController::Parameters.new(#attr)
User.create!(param.require(:user).permit!)
That usage would match the usual:
def some_controller_method
#user = User.create(user_param)
end
def user_param
param.require(:user).permit!
end
usage in controllers.
Related
I am setting up RSpec request tests, and I have the following test:
require 'rails_helper'
RSpec.describe "ClientApi::V1::ClientContexts", type: :request do
describe "POST /client_api/v1/client_contexts" do
let(:client_context) { build :client_context }
it "creates a new context" do
post "/client_api/v1/client_contexts", params: {
browser_type: client_context.browser_type,
browser_version: client_context.browser_version,
operating_system: client_context.operating_system,
operating_system_version: client_context.operating_system_version
}
expect(response).to have_http_status(200)
expect(json.keys).to contain_exactly("browser_type", "browser_version", "operating_system", "operating_system_version")
# and so on ...
end
end
end
The corresponding factory is this:
FactoryBot.define do
factory :client_context do
browser_type { "Browser type" }
browser_version { "10.12.14-blah" }
operating_system { "Operating system" }
operating_system_version { "14.16.18-random" }
end
end
Now, obviously, that all seems a bit redundant. I have now three places in which I specify the attributes to be sent. If I ever want to add an attribute, I have to do it in all of these places. What I actually want to do is send the particular attributes that the Factory specifies via POST, and then check that they get returned as well.
Is there any way for me to access the attributes (and only these!) that I defined in the Factory, and re-use them throughout the spec?
I should prefix this with a warning that abstracting away the actual parameters from the request being made could be seen as detrimental to the overall test expressiveness. After all, now you'd have to look into the Factory to see which parameters are sent to the server.
You can simply get the Factory-defined attributes with attributes_for:
attributes_for :client_context
If you need more flexibility, you can implement a custom strategy that returns an attribute Hash from your Factory without creating the object, just building it.
Create a file spec/support/attribute_hash_strategy.rb:
class AttributeHashStrategy
def initialize
#strategy = FactoryBot.strategy_by_name(:build).new
end
delegate :association, to: :#strategy
def result(evaluation)
evaluation.hash
end
end
Here, the important part is evaluation.hash, which returns the created object as a Ruby Hash.
Now, in your rails_helper.rb, at the top:
require 'support/attribute_hash_strategy'
And below, in the config block, specify:
# this should already be there:
config.include FactoryBot::Syntax::Methods
# add this:
FactoryBot.register_strategy(:attribute_hash, AttributeHashStrategy)
Now, in the Spec, you can build the Hash like so:
require 'rails_helper'
RSpec.describe "ClientApi::V1::ClientContexts", type: :request do
describe "POST /client_api/v1/client_contexts" do
let(:client_context) { attribute_hash :client_context }
it "creates a new context" do
client = create :client
post "/client_api/v1/client_contexts",
params: client_context
expect(response).to have_http_status(200)
end
end
end
The attribute_hash method will be a simple Hash that you can pass as request parameters.
I am currently trying to write unit tests for user authentication in rails and I keep running into a problem.
I am trying to test the following method:
def reset_session_token!
self.session_token = User.generate_session_token
self.save!
self.session_token
end
with the following unit test:
let(:valid_user) { User.new(user_name: 'Name', password: 'abcdefghijkl')}
it "should set the session_token" do
valid_user.reset_session_token!
expect(valid_user.session_token).not_to be_nil
end
but the test fails with the error Validation failed: User name has already been taken. I suspect that it is because reset_session_token! calls save on the user instance but this is necessary for the method to work properly. How can I get around this?
Thanks to Oleg Sobchuk's comment I was able to figure out how to deal with the save! method.
Since stub has been deprecated I used allow_any_instance_of with receive instead. Here is what I did:
it "should set the session_token" do
allow_any_instance_of(User).to receive(:save!).and_return(nil)
valid_user.reset_session_token!
expect(valid_user.session_token).not_to be_nil
end
I have a class for constructing some user parameters by getting a section of params from a post request and adding some user agent information to them. I'm using the browser gem for this purpose.
The gem adds a helper method called browser, that inspects your current user agent. Its use is as simple as this:
require "browser"
browser.name # readable browser name
browser.version # major version number
A short section of my class looks like so:
class AkUserParams
def self.create(params)
#user_params = params[:signature]
#user_params[:user_agent] = browser.user_agent
#user_params
end
end
Using the browser gem throughout my application works fine. However, when I write a spec in RSpec for the class, I get the following error:
NameError: undefined local variable or method 'browser' for AkUserParams:Class
describe AkUserParams do
# I have tried both with and without this before block:
before :all do
$browser = Browser.new
end
let(:some_params) {{
signature: {
name: Faker::Name.name,
email: Faker::Internet.email,
}
}}
# This is a bogus test I'm expecting to fail. It doesn't get to
# failing and instead gives me a name error.
it 'Builds an object containing data about the user and the action' do
expect(AkUserParams.create(petition_signature)).to eq(true)
end
end
Update: I fixed the issue by just injecting browser:
class AkUserParams
def self.create(browser, params)
#user_params = params[:signature]
#user_params[:user_agent] = browser.user_agent
#user_params
end
end
That way I can just let(:browser) { Browser.new } in the spec.
I'm new to Rails and Rspec and I'm using Rspec to test this controller method which includes exception handling:
def search_movies_director
#current_movie = Movie.find(params[:id])
begin
#movies = Movie.find_movies_director(params[:id])
rescue Movie::NoDirectorError
flash[:warning] = "#{#current_movie} has no director info"
redirect_to movies_path
end
end
I can't figure out how to correctly test the said path: after invalid search (when error is received) it should redirect to the homepage. I tried something like this:
describe MoviesController do
describe 'Finding Movies With Same Director' do
#some other code
context 'after invalid search' do
it 'should redirect to the homepage' do
Movie.stub(:find)
Movie.stub(:find_movies_director).and_raise(Movie::NoDirectorError)
get :search_movies_director, {:id => '1'}
response.should redirect_to movies_path
end
end
end
end
After running the test fails with an error: NameError: uninitialized constant Movie::NoDirectorError
How to fake raising an error in this test so it actually checks whether redirect happens?
Thanks!
UPDATE:
As nzifnab explained, it couldn't locate Movie::NoDirectorError. I forgot to define this exception class. So I added it to app/models/movie.rb :
class Movie < ActiveRecord::Base
class Movie::NoDirectorError < StandardError ; end
#some model methods
end
This solved my problem and this test passes.
You're very close. You need to add any_instance in there.
Movie.any_instance.stub(:find_movies_director).and_raise(Movie::NoDirectorError)
edit: I misread the post. The above would work given an instance of Movie, but not for OP's question.
The error indicates it doesn't know where that Movie::NoDirectorError exception class is defined. You might need to specifically require the file where that class is defined or the test may not be able to find it.
Rails will automatically attempt to locate any constant missing constants using a conventional file directory format. It will look for a file in the load_path at movie/no_director_error and movie based on the name of the constant. If the file is not found or the file doesn't define the expected constant than you'll need to specifically require the file yourself.
In Rails 4.1:
verse_selector = double("VerseSelector", select_verses: ActiveRecord::RecordNotFound.new("Verses not found"))
verse_selector.select_verses will now return an ActiveRecord::RecordNotFound
I have a still pretty simple Rails application that I want to develop using BDD with Cucumber and TDD with RSpec. Currently, I am hanging at a test where I want to check that if a new instance of an Organizer (that's the model I have) cannot be created due to a validation error. I would like to check that the errors Array of the object to be created is not empty so that I can be sure that error messages are available for showing them in the view.
require 'spec_helper'
describe OrganizersController do
render_views
describe "POST 'create'" do
describe "with invalid arguments" do
before(:each) do
request.env["HTTP_REFERER"] = organizers_new_path
#organizer_args = { :name => "" }
end
it "should return a non-empty list of errors" do
post 'create', :organizer => #organizer_args
#organizer.errors.empty?.should_not be_true
end
end
end
end
I am developing based on Rails 3.2.9 with RSpec 2 and cucumber-rails.
Any suggestions are appreciated. Thanks!
You should use assigns method to get instance variable from controller action:
assigns(:organizer).errors.empty?.should_not be_true
The latest preferred syntax is:
expect(assigns(:organizer).errors.empty?).to_not be_true
thanks for the answer guys but I'd like to suggest a slightly nicer syntax:
expect(assigns(:organizer).errors).to_not be_empty
(unrelated to the question 👇)
Basically whenever you have a method that ends with ? you'll have the corresponding rspec matcher that starts with be_ e.g.
1.odd? #=> true
expect(1).to be_odd