I am trying to create a mock object and i am checking whether my method receives the right param and expected result.
Below is my spec.
require 'spec_helper'
describe User do
let(:username) {"test#test.com"}
let(:password) {"123"}
let(:code) {"0"}
context "when signing in" do
it "should sign in" do
user = double("user")
expected_results = {
"token": "123"
}
allow(user).to receive(:login).with({email: username, password: password, code: code})
.and_return(expected_results)
expect(user.login).to eq(expected_results)
end
end
end
Is there a way to separate my json from it block and keep it outside?.
You can use let inside a context block to set the value of a variable for the examples nested within:
require 'spec_helper'
describe User do
let(:username) {"test#test.com"}
let(:password) {"123"}
let(:code) {"0"}
context "when signing in" do
let(:expected_results) { {token:"123"}.to_json }
it "should sign in" do
user = double("user")
allow(user).to receive(:login).with({email: username, password: password, code: code})
.and_return(expected_results)
expect(user.login).to eq(expected_results)
end
end
end
am i doing the right way of testing?
Not if you are testing the User#login method. You should not set a stub if you are trying to test the logic of the method being stubbed. Instead, use a real model instance, perhaps using a factory, and omit the allow step.
You can use a before do block in the context or before the context.
Here is the reference:
https://www.relishapp.com/rspec/rspec-core/docs/hooks/before-and-after-hooks
Related
Hi i have written Rspec to validate a body of message\n
describe "validate_msm" do
let!(:user) { FactoryGirl.create(:user) }
it "should contains welcome user" do
body = user.send_sms("909090990")
expect(body).to include("welcome")
end
end
as send_sms will call the send method which i have mentioned in let!
def send_sms
...
..
body="welcome user"
Message.send(contact, body)
end
so how to check with the body content is equal to welcome user ,
as send_sms doesn't return anything, so how to checks the value present in the body variable in rspec
You don't. Or rather you don't that easily. Most of the libraries like this should come together with test adapters and helpers to make such testing possible. If this one does not, you can only test that the message has been sent with correct arguments:
it "should contains welcome user" do
allow(Message).to receive(:send)
user.send_sms("909090990")
expect(Message).to have_received(:send) do |_contact, body|
expect(body).to include "welcome"
end
end
I am new to Rspec, i am trying to write a test for a simple feature.
A user creates a contract, if the contract the is created and has a specific value on its property then i send an email to notify the teacher.
How do i write the condition in the test?
I am using letter_opener and ActionMailer.
describe "When a user creates a apprentice contract" do
let(:admin) { users(:admin) }
before { signin admin }
it "should send an email to the teacher" do
contract = create(:contract)
contract.education_type.must_equal("company_apprentice")
end
end
when contract.education_type.must_equal("school_apprentice") is true i want to test if it sends an email.
How do i write that in this test?
it 'should send an email to the teacher' do
expect { create(:contract, education_type: 'company_apprentice' }
.to change { ActionMailer::Base.deliveries.count }
.by(1)
end
you also need this set:
# config/environments/test.rb
config.action_mailer.delivery_method = :test
I am using let to create a user record using factory girl. However i want to use exactly the same variable across 2 tests in the context as the user_id and email are important to the external API i am sending.
However i had no luck making a single variable for using across the examples. Here is my current code
context "User" do
let(:user) { FactoryGirl.create(:user) }
it "should create user and return 'nil'" do
expect(send_preferences(user, "new")).to eq nil
end
it "should not create user preferences again after sending two consecutive same requests" do
expect(send_preferences(user, "new")).to eq "User preferences already saved. No need to re-save them."
end
it "should update user preferences" do
expect(send_preferences(user, "update")).to eq nil
end
end
any clues?
You can use lets within lets:
context "User" do
let(:email_address) { 'test#test.com' }
let(:user) { FactoryGirl.create(:user, email_address: email_address) }
You will then also have access to the email_address variable within all your tests.
This works because previously the email address was being randomly generated by the factory every time the user was created, as we hadn't set a value for it anywhere. So, we called the code below in each test:
send_preferences(user, "new")
It called the 'user' let which created a new user with a completely random email address (as we hadn't give it a specific email value). Therefore during the backend API call it was sending a different email address every time.
let(:user) { FactoryGirl.create(:user) }
However, when we defined the email address 'let' as 'test#test.com', and passed that into the user factory as in the code I provided, we overrode the randomly generated email address with our own static value, So, every time we call the code again:
send_preferences(user, "new")
It now triggers the user factory create which is also taking our new 'email_address' let, which is always set to a specific value of test#test.com every time it is called.
let(:email_address) { 'test#test.com' }
let(:user) { FactoryGirl.create(:user, email_address: email_address) }
Therefore, when the backend API call is made the email address is always what we set it to.
Also, as it is a let we can use that variable in any of the tests themselves if we wish. For example:
it 'should set the email address' do
expect(user.email_address).to eq(email_address)
end
It's quite hard to explain in a few sentences but let me know if that's still not clear.
Having an instantiated variable shared among multiple tests is an anti-pattern 90% of the time in my opinion.
The problem with doing something like the below is you will be creating objects in your db without doing a cleanup.
before(:all) do
#user = FactoryGirl.create :user
end
Sure, you can do a before(:after) block or use DatabaseCleaner, but I think it is much better practice for tests to be as standalone as possible. In your case, make your setup of a send_preferences event before making an expectation on what happens the second time:
context "User" do
let(:user) { FactoryGirl.create(:user) }
# ...
it "should not create user preferences again after sending two consecutive same requests" do
send_preferences(user, "new") # Setup
expect(send_preferences(user, "new")).to eq "User preferences already saved. No need to re-save them."
end
it "should update user preferences" do
send_preferences(user, "new") # Setup
expect(send_preferences(user, "update")).to eq nil
end
end
First of all I should probably mention that I'm very new to Rails and this is my first "serious" project, so I apologise if this is a simple question but I can't find an answer for it.
I'm using TDD in my project and am using RSpec to write the model tests, FactoryGirl to create the models and Faker to create dummy data for the models. Everything has been going really well until I added a test to make sure no two users have the same email address. In my User model I validated it like so:
# /app/models/user.rb
validates :email, :password_reset_code, :auth_token, uniqueness: true
My factory creates a user model with Faker, like so:
# /spec/factories/users.rb
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
password { Faker::Internet.password }
password_reset_code { Faker::Lorem.word }
auth_token { Faker::Lorem.word }
end
end
and my user_spec.rb test for this is as follows:
# /spec/models/user_spec.rb
it "is invalid with a duplicate email" do
user = FactoryGirl.create(:user)
FactoryGirl.create(:user, email: user.email).should_not be_valid
end
Here I'm creating a new model with FactoryGirl using its dummy values from Faker, saving it to the database and then creating another one with the same email as the first one. I'd expect RSpec to tell me this test passed because of the should_not be_valid part. But instead I get this output when I run the test:
Failures:
1) User is invalid with a duplicate email
Failure/Error: FactoryGirl.create(:user, email: user.email).should_not be_valid
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken
# ./spec/models/user_spec.rb:19:in `block (2 levels) in <top (required)>'
So it seems that the model validation is raising an error which RSpec isn't catching and using to pass the test? I've managed to work around it by changing the test to this:
it "is invalid with a duplicate email" do
begin
user = FactoryGirl.create(:user)
FactoryGirl.create(:user, email: user.email).should_not be_valid
rescue
false
end
end
which seems to work, however I have a feeling this isn't the best way to do it.
What's the proper way to write this test?
I ran into this problem too. The error you're encountering is due to the fact that the create() method actually persists the model, which is then throwing an ActiveRecord::RecordInvalid at the DB layer (well, the persistence layer). If you want to assert if a model is valid or not you should use the build() method for your second object and then ask if it's valid. You can read up on it in this post.
Additionally, if you're just trying to test various validations on models and what not I wrote a quick and dirty gem that you can use to assert some of the more basic model validations. You can check it out here.
Hope that helps.
I would go with:
# /spec/models/user_spec.rb
describe 'validations' do
context 'with a duplicate email' do
let(:other_user) { FactoryGirl.create(:user) }
let(:attributes) { FactoryGirl.attributes_for(:user) }
subject(:user) { User.new(attributes.merge(email: user.email)) }
it 'is not valid' do
expect(user).to_not be_valid
end
end
end
I wrote up a test that should describe the case where #user and found_user should be the same via password match. This also describes when they're different. I'm not using devise or anything, but rather building out my own authentication with has_secure_password
describe "return value of authenticate method" do
before { #user.save }
let(:found_user) { User.find_by(email: #user.email) }
describe "with a valid password" do
it { should eq found_user.authenticate(#user.password) }
end
describe "with an invalid password" do
let(:user_for_invalid_password) { found_user.authenticate('invalid') }
it { should_not eq user_for_invalid_password }
specify { expect(user_for_invalid_password).to be_false }
end
end
The part that's failing is with a valid password block. The error message clearly shows that password_digest isn't matching up
Here's the relavent output:
expected: password_digest: "$2a$04$cDKhuWzsZuW8Gm4t5fJjpu6rmbwh10ZAt2Yae.BO0iuD...">
got: password_digest: "$2a$04$jwfHjoLI0RpDIAEr9SMKGOZqeH.J5ILOkzalKCYQdDW4...">
I've attempted removing the #user.save in the before block thinking that might solve it, but it didn't.
I'm not really sure why they're coming up differently or what it is that I'm doing wrong. I'm fairly new to rspec and testing in general.
I should mention that my authenticate method is working in the rails console. So I have a situation where the application code works, but the tests are failing.
Any help would be much appreciated.
My user class is here: https://gist.github.com/DavidVII/f190d1f1e114234bb7d7
Thnx!
Your test won't work with bcrypt password hashing (I can tell this what you are using from the form of the string), as it generates a new random salt for each password change. This is a good thing, don't alter that behaviour.
So you should not write tests that look for stored passwords being equal to known values. It is not 100% clear if you intended that in the test, or have accidentally over-simplified the test due to all the abstraction you get though using has_secure_pasword, or some other thing has led you to the current code.
Instead, your tests around password handling should be more black box, and assert that you can log in with the known password, and not login with any others (including code-breaking cases such as nils, empty strings, super-long passwords and a string which matches the hashed password).
describe "with an invalid password" do
let(:#user_for_invalid_password) { found_#user.authenticate('invalid') }
it { should_not eq #user_for_invalid_password }
specify { expect(#user_for_invalid_password).to be_falsey}
end
end