I have a several models and in this models I have attributes that I don't want to be blank / empty.
I want to heavily test my models on these restrictions using RSpec and Factory Girl.
However I end up with code duplication:
user_spec:
it 'is invalid if blank' do
expect {
FactoryGirl.create(:user, nickname => '')
}.to raise_error(ActiveRecord::RecordInvalid)
end
message_spec:
it 'is invalid if blank' do
expect {
FactoryGirl.create(:message, :re => '')
}.to raise_error(ActiveRecord::RecordInvalid)
end
How can I factor it ?
RSpec provides several ways to do so, such as Shared Examples.
1. Create a file in your [RAILS_APP_ROOT]/support/
Based on your example, you could name this file not_blank_attribute.rb. Then, you just have to move your duplicated code in and adapting it to make it configurable :
RSpec.shared_examples 'a mandatory attribute' do |model, attribute|
it 'should not be empty' do
expect {
FactoryGirl.create(model, attribute => '')
}.to raise_error(ActiveRecord::RecordInvalid)
end
end
2. Use it_behaves_like function in your specs
This function will call the shared example.
RSpec.describe User, '#nickname' do
it_behaves_like 'a mandatory attribute', :User, :nickname
end
Finally, it outputs:
User#nickname
behaves like a mandatory attribute
should not be empty
Related
I have the following code:
context 'user doesnt exists with that email' do
let(:params) { original_params.merge(login: "nouser#example.org") }
it_behaves_like '404'
it_behaves_like 'json result'
it_behaves_like 'auditable created'
end
It is dry because I can use these elements in other contexts as well:
context 'user exists with that email' do
it_behaves_like '200'
it_behaves_like 'json result'
end
My shared_example is:
...
RSpec.shared_examples "json result" do
specify 'returns JSON' do
api_call params, developer_header
expect { JSON.parse(response.body) }.not_to raise_error
end
end
...
The benefits are that the spec is more readable and is dry. The spec failure points to the shared_example file rather than the original spec. It is hard to debug.
The following error occurs at login_api_spec:25, but this is rspecs output:
rspec ./spec/support/shared_examples/common_returns.rb:14 # /api/login GET /email user doesnt exists with that email behaves like 401 returns 401
Any good advice how to proceed to write both dry and easy to debug rspec?
Without shared examples the code would be much longer and not as easy to read:
context 'user doesnt exists with that email' do
let(:params) { original_params.merge(login: "nouser#example.org") }
specify "returns 404" do
api_call params, developer_header
expect(response.status).to eq(404)
end
specify 'returns JSON' do
api_call params, developer_header
expect { JSON.parse(response.body) }.not_to raise_error
end
specify 'creates an api call audit' do
expect do
api_call params, developer_header
end.to change{ EncoreBackend::ApiCallAudit.count }.by(1)
end
end
I have thousands of RSpec tests like this so it is very beneficial to write the specs with shared examples because it is fast to write, but the debugging is hard.
Amongst the detailed errors there is description like this:
Shared Example Group: "restricted_for developers" called from ./spec/api/login_api_spec.rb:194
This tells the exact place of the error
I want to reuse this shared_examples block across different spec files. I want to extract it into a separate file, and pass in the object so it's not always user. Both things I tried failed, is it possible?
describe User do
before { #user = build_stubbed(:user) }
subject { #user }
shared_examples 'a required value' do |key| # trivial example, I know
it "can't be nil" do
#user.send("#{key}=", nil)
#user.should_not be_valid
end
end
describe 'name'
it_behaves_like 'a required value', :name
end
end
Just require the other file. shared_examples work at the top level, so once defined they are always available; so be careful of naming conflicts.
A lot of RSpec users will put the shared example in spec/support/shared_examples/FILENAME.rb. Then in spec/spec_helper.rb have:
Dir["./spec/support/**/*.rb"].sort.each {|f| require f}
Or on Rails projects
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
That is listed in the 'CONVENTIONS' section of the shared example docs.
I'm writing some specs that test the template files in a gem that has generators for Rails. I'd love to access to "admin_layout.html.erb" in the rspec spec below:
require 'spec_helper'
describe "admin_layout.html.erb" do
it "has page title Admin" do
HERES WHERE I WOULD LOVE TO HAVE ACCESS TO "admin_layout.html.erb" AS A VARIABLE
end
end
You can use self.class.description to get this info:
it "has page title Admin" do
layout = self.class.description
# => "admin_layout.html.erb"
end
However, keep in mind this will only put out the first parent's description. So if you have contexts in your describe block, then the examples within the contexts would give the context name for self.class instead of the describe block's name. In that case, you could use metadata:
describe "admin_layout.html.erb", :layout => "admin_layout.html.erb"
context "foo" do
it "has page title Admin" do
layout = example.metadata[:layout]
end
end
end
In case you want the top-level description, you can use self.class.top_level_description:
RSpec.describe "Foo", type: :model do
context "bar" do
it "is part of Foo" do
self.class.top_level_description
# => "Foo"
end
end
end
I'm trying to create a test case for User model. Basically, it will validate first_name and last_name to be present.
What I am trying to do is to check whether the error on a specific field is empty or not and it should be empty. However it always fails.
What is the correct way to do this?
Here is my code
On my user_spec.rb
require 'spec_helper'
describe User do
before do
#user = User.new
end
it "must have a first name" do
#user.errors[:first_name].should_not be_empty
end
it "must have a last name" do
#user.errors[:last_name].should_not be_empty
end
end
On my user.rb file
class User < ActiveRecord::Base
validates :first_name, :presence => true
validates :last_name, :presence => true
end
You can test by simply doing this as well:
describe 'validations' do
it { should validate_presence_of :firstname }
it { should validate_presence_of :lastname }
end
Take a look at the shoulda matchers for all such standard Rails Validation.
This way is not just more concise but also takes care of the positive case. Meaning you then dont need to test the scenario mentioned below:
it "passed validations when first_name is set"
user = User.create(:firstname => 'f', :lastname => 'l')
user.errors[:first_name].should be_empty
user.errors[:last_name].should be_empty
end
RSpec supports the notion of an "implicit" subject. If your first argument to the "describe" block is a class, RSpec automatically makes an instance of that class available to your specs. See http://relishapp.com/rspec/rspec-core/v/2-6/dir/subject/implicit-subject.
require 'spec_helper'
describe User do
it "must have a first name" do
subject.should have(1).error_on(:first_name)
end
it "must have a last name" do
subject.should have(1).error_on(:last_name)
end
end
which results in RSpec output (if using --format documentation) of:
User
must have a first name
must have a last name
You can abbreviate it even further if you are content with the RSpec output defaults:
require 'spec_helper'
describe User do
it { should have(1).error_on(:first_name) }
it { should have(1).error_on(:last_name) }
end
which results in:
User
should have 1 error on :first_name
should have 1 error on :last_name
I'm writing a few helpers to DRY up my tests. I pictured something like:
class ActiveSupport::TestCase
def self.test_presence_validation_of model, attribute
test "should not save #{model.to_s} with null #{attribute.to_s}", <<-"EOM"
#{model.to_s} = Factory.build #{model.to_sym}, #{attribute.to_sym} => nil
assert !#{model.to_s}.save, '#{model.to_s.capitalize} with null #{attribute.to_s} saved to the Database'
EOM
# Another one for blank attribute.
end
end
So that this:
class MemberTest < ActiveSupport::TestCase
test_presence_validation_of :member, :name
end
Executes exactly this at MemberTest class scope:
test 'should not save member with null name' do
member = Factory.build :member, :name => nil
assert !member.save, 'Member with null name saved to the Database'
end
Is it possible to do it this way (with a few adaptations, of course; I doubt my "picture" works), or do I have to use class_eval?
Have you seen Shoulda? It's great for testing common Rails functionality such as validations, relationships etc. https://github.com/thoughtbot/shoulda-matchers
In this case, it seems class_eval is necessary since I want to interpolate variable names into actual code.
Illustrated here.