here is my rspec code:
describe User do
before{(#user=User.new(username:"abcdefg",email:"123456#123.com",password:"123456")}
subject(#user)
#user.save
end
and I got such an error : undefined method 'save' for nil:NilClass(NoMethodError)
I try to write the same code in the rails console,it just worked. But when it comes to Rspec,it failed and I'm not able to find any reason...
Could any one help me with it?
here is the Rspec way:
describe User do
let(:valid_user) { User.new(username:"abcdefg",email:"123456#123.com",password:"123456") }
it "can be saved" do
expect(valid_user.save).to be_true
end
end
Note that you should avoid database operations in your specs, it's what make them slow.
Another point, consider using factories to clean up your specs.
You need to wrap the code in an example block (i.e., call the it method with a block), because in the context of the describe block, #user is not defined. For example:
describe User do
before{(#user=User.new(username:"abcdefg",email:"123456#123.com",password:"123456")}
subject(#user)
it "can be saved" do
#user.should respond_to(:save)
#user.save.should_not be_false
end
end
Edit: I noticed also that you have subject(#user) but that may need to be a block in order to set it properly. The following is cleaner overall:
describe User do
let(:user) { User.new(username:"abcdefg",email:"123456#123.com",password:"123456") }
it "can be saved" do
user.should respond_to(:save)
user.save.should_not be_false
end
end
Related
Let's say I have various RSpec context blocks to group tests with similar data scenarios.
feature "User Profile" do
context "user is active" do
before(:each) { (some setup) }
# Various tests
...
end
context "user is pending" do
before(:each) { (some setup) }
# Various tests
...
end
context "user is deactivated" do
before(:each) { (some setup) }
# Various tests
...
end
end
Now I'm adding a new feature and I'd like to add a simple scenario that verifies behavior when I click a certain link on the user's page
it "clicking help redirects to the user's help page" do
click_on foo_button
expect(response).to have('bar')
end
Ideally I'd love to add this test for all 3 contexts because I want to be sure that it performs correctly under different data scenarios. But the test itself doesn't change from context to context, so it seems repetitive to type it all out 3 times.
What are some alternatives to DRY up this test set? Can I stick the new test in some module or does RSpec have some built in functionality to let me define it once and call it from each context block?
Thanks!
You can use shared_examples ... define them in spec/support/shared_examples.rb
shared_examples "redirect_help" do
it "clicking help redirects to the user's help page" do
click_on foo_button
expect(response).to have('bar')
end
end
Then in each of your contexts just enter...
it_behaves_like "redirect_help"
You can even pass a block to it_behaves_like and then perform that block with the action method, the block being unique to each context.
Your shared_example might look like...
shared_examples "need_sign_in" do
it "redirects to the log in" do
session[:current_user_id] = nil
action
response.should render_template 'sessions/new'
end
end
And in your context you'd call it with the block...
describe "GET index" do
it_behaves_like "need_sign_in" do
let(:action) {get :index}
end
...
I'm trying to do some model_spec testing but having trouble with not having to further nest my rspec code. It would be great if in this case, I could just have a set of "it's" instead of having to add context everytime I want to switch the variable var. Here's the following code:
describe "#some_method" do
subject { course.some_method(var) }
context 'given a project' do
let(:var) {random[1]}
it 'returns the one after' do
is_expected.to eq(random[2])
end
context 'being the last' do
let(:vars) {random.last}
it 'returns nil' do
is_expected.to be_nil
end
end
context '...you get the point, being something else' do
let(:vars) { something.else }
it 'returns nil' do
is_expected.to.to be_nil
end
end
end
end
Maybe I'm just stuck in the wrong mode of thinking and someone could think of a better way for me to do this? I've been suggested that I absolutely must use the subject by someone I work for.
At first, I disagreed and thought it was getting a little burdensome but then I figured keeping subject and having let(:var) apply to it was pretty useful...
RSpecs subject is a tool which can be used to make tests more succinct. There are many cases where it makes sense to use the subject:
RSpec.describe User do
# with the help of shoulda-matchers
it { should validate_uniqueness_of :username } # implicit subject
end
RSpec.describe UsersController do
describe '#show' do
it 'is successful' do
get :show
expect(response).to have_http_status :success
end
it 'renders template show' do
get :show
expect(response).to render_template :show
end
end
#vs
describe '#show' do
subject { response }
before { get :show }
it { should have_http_status :success }
it { should render_template :success }
end
end
And there are cases where using subject will hurt the readability and acuity of your tests.
Your college is just plain wrong in insisting that you always use subject.
A good rule of hand is that if you need an it block then you should not be using subject or is_expected.
If you are describing the call signature of a method you should be calling it in your specs in the same way you would in real life.
let(:decorator){ described_class.new(user) }
describe "#link" do
it 'takes a class option' do
expect(decorator.link(class: 'button')).to match /class=\"button/
end
end
I would recommend running rspec with the --format documentation option and checking if the output actually makes sense. This can be quite important once you get 100s of specs as it gets harder to remember what a behavior a spec actually covers.
How about you write it like this?
expect(subject.call(foo)) is not very pretty but it gets rid of the nesting.
describe "#some_method" do
subject { course.method(:some_method) }
it 'returns the one after if given a project' do
expect(subject.call(random[1])).to eq(random[2])
end
it 'returns nil when it is the last' do
expect(subject.call(random.last)).to be_nil
end
it 'returns nil...' do
expect(subject.call(something.else)).to be_nil
end
end
I am current getting my tests to pass, but I wanted to see if I could refactor the tests with a let(:message) or some kind of variable
Mailer tests
describe "Contact Form" do
context "when a valid message" do
it "sends an email" do
post contact_create_path, message: FactoryGirl.attributes_for(:message)
expect(ActionMailer::Base.deliveries.last.to).to eq(["#{ENV["MVP_USERNAME"]}"])
end
end
The part I want to refactor is message: FactoryGirl.attributes_for(:message).
I tried to doing something like
context "when a valid message" do
let(:message) { FactoryGirl.attributes_for(:message) }
it "sends an email" do
post contact_create_path, message
expect(ActionMailer::Base.deliveries.last.to).to eq(["#{ENV["MVP_USERNAME"]}"])
end
but that outputs
ActionController::ParameterMissing:
param not found: message
other attempts
#message = FactoryGirl.attributes_for(:message)
message = FactoryGirl.attributes_for(:message)
I could leave it like this, but I feel that I it should be changed for some reason. Suggestions?
It doesn't look to be in need of refactoring to me, but the trouble is the version you have in your let statement is simply a hash of attributes. It's missing the :message key.
let(:message) { message: FactoryGirl.attributes_for(:message) }
That should work, but as I said I don't think you need to refactor it. I'd actually just make sure you're mixing in the FactoryGirl syntax methods into your specs so you can drop the FactoryGirl and just use attributes_for directly.
In your spec helper:
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
I am trying to refactor some RSpec/Rails tests so that they persist as few objects to the database as possible, but am having trouble trying to figure out how to re-write tests like the following:
describe User do
context "record creation" do
before(:each) { #user = User.new(user_atts) }
it "should generate a confirmation_token" do
# Generated as the result of a callback
#user.save!
expect(#user.confirmation_token).to be_present
end
it "should set the confirmed_at attribute to nil" do
# Cleared as the result of a callback
#user.save!
expect(#user.confirmed_at).to be_nil
end
it "should call the send_confirmation_instructions method" do
#user.should_receive(:send_confirmation_instructions) {}
#user.save!
end
end
def user_atts
# return attributes hash
end
end
This is a pretty simple example, but there are plenty of similar instances in my specs, and, for the most part, they all persist records to the database. I would love to take advantage of RSpec's let and subject helpers, but am not fully sure that those would even help here.
I have been using FactoryGirl a lot and thought that maybe its build_stubbed strategy would speed up my specs a bit, but I couldn't find many instances where it would help limit actual record creation (or maybe I don't know how to use).
I assume there are some cases where a test requires record creation, but the above example hardly seems like one of them. Should I even be trying to refactor this or is there a better to write these tests? Any help would be greatly appreciated.
My tests would probably look something like this.
describe User do
let(:user) { FactoryGirl.build_stubbed(:user) }
context "record creation" do
it "should generate a confirmation_token" do
user.save!
expect(user.confirmation_token).to be_present
end
it "should set the confirmed_at attribute to nil" do
user.save!
expect(user.confirmed_at).to be_nil
end
it "should call the send_confirmation_instructions method" do
expect(user).to receive(:send_confirmation_instructions).once
user.save!
end
end
end
That's using Factory Girl to create the user models. Also, I'd have DatabaseCleaner to clear the database after each test as stated by #RahulGarg
All you'd have to do is configure in your spec_helper something like this
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
This means after each test the Database would be cleared.
I'm an RSpec newb, but am really loving how easy it is to write the tests and I'm continually refactoring them to be cleaner as I learn new features of RSpec. So, originally, I had the following:
describe Account do
context "when new" do
let(:account) { Account.new }
subject { account }
it "should have account attributes" do
subject.account_attributes.should_not be_nil
end
end
end
I then learned about the its method, so I tried to rewrite it as such:
describe Account do
context "when new" do
let(:account) { Account.new }
subject { account }
its(:account_attributes, "should not be nil") do
should_not be_nil
end
end
end
This fails due to its not accepting 2 arguments, but removing the message works just fine. The issue is that if the test fails, the message under the Failed examples section just says
rspec ./spec/models/account_spec.rb:23 # Account when new account_attributes
which isn't overly helpful.
So, is there a way to pass a message to its, or better yet, have it output a sane message automatically?
You could define an RSpec custom matcher:
RSpec::Matchers.define :have_account_attributes do
match do |actual|
actual.account_attributes.should_not be_nil
end
failure_message_for_should do
"expected account_attributes to be present, got nil"
end
end
describe Account do
it { should have_account_attributes }
end
You can also write: its(:account_attributes) { should_not be_nil }
See https://www.relishapp.com/rspec/rspec-core/v/2-14/docs/subject/attribute-of-subject
Take note that "its" will be extracted from rspec-core to a gem with the release of rspec 3, though.
Looks like a relatively simple monkey-patch will enable what you seek.
Look at the source of the rspec-core gem version you're using. I'm on 2.10.1. In the file lib/rspec/core/subject.rb I see the its method defined.
Here's my patched version - I changed the def line and the line after that.
Caution - this is very likely to be version specific! Copy the method from your version and modify it just like I did. Note that if the rspec-core developers do a major restructuring of the code, the patch may need to be very different.
module RSpec
module Core
module Subject
module ExampleGroupMethods
# accept an optional description to append
def its(attribute, desc=nil, &block)
describe(desc ? attribute.inspect + " #{desc}" : attribute) do
example do
self.class.class_eval do
define_method(:subject) do
if defined?(#_subject)
#_subject
else
#_subject = Array === attribute ? super()[*attribute] : _nested_attribute(super(), attribute)
end
end
end
instance_eval(&block)
end
end
end
end
end
end
end
That patch can probably be put in your spec_helper.rb.
Now the usage:
its("foo", "is not nil") do
should_not be_nil
end
Output on failure:
rspec ./attrib_example_spec.rb:10 # attr example "foo" is not nil
If you omit the second arg, the behavior will be just like the unpatched method.