Mocking the model and its methods - ruby-on-rails

Can anyone tell me whether my approach is fine?
I want to know the api we are using is working as designed and when it changes so we know that has changed without having to dig through all the code logic. We will provide a set of arguments, we expect a certain result so that my unit tests work well.
User.login({email: username, password: password);
The above method in my model actually hits the API and returns me a response. I want to check whether my Model's login method work as intended.
Below is my approach.
I am stubbing my login method in my model with required params and expected response, to avoid hitting the api and then expecting the login method to derive the same response.
I am using ActiveRestClient.
Below is my model
class User << ActiveRestClient::Base
get :all, '/user'
get :find, '/user/:id'
end
Below is my spec
require 'spec_helper'
describe User do
let(:username) {"test#test.com"}
let(:password) {"123"}
context "when signing in" do
let(:response) {{token: "123"}.to_json}
it "should sign in with valid input" do
allow(User).to receive(:login).with({email: username, password:
password}).and_return(response)
expect(User.login({email: username, password: passwor})).to eq(response)
end
end
end
Can anyone tell me whether my approach is fine?

No, I am sorry, your approach is not fine. Because it does not test a single line of our code. The only thing your spec is testing is that the User.login stub returns what you told it to return.
If you want to speed up your specs by stubbing methods, than you should look for calls in your method that touch the database. Something like User.find_by_email in the following example (And I guess you do something similar in your login method).
Furthermore you may want to spec want happens if the email or the password does not match.
describe User do
describe 'login' do
let(:username) { "test#test.com" }
let(:password) { "password" }
subject(:login) { User.login(email: username, password: password) }
context 'when user do not exists' do
before { allow(User).to receive(:find_by_email).and_return(nil) }
it 'returns nil' do
expect(login).to be_nil
end
end
context 'when user exists' do
before do
allow(User).to receive(:find_by_email).with(username).and_return(user)
end
context 'when password does not match' do
let(:user) { User.new(:password => 'wrong password') }
it 'returns nil' do
expect(login).to be_nil
end
end
context 'when password matches' do
let(:user) { User.new(:password => password, :generate_token => 123) }
it 'returns a json containing the signin token' do
expect(login).to eq "{'token':'123'}"
end
end
end
end
end
Since I have no idea what your login method really does, all specs above are just based on assumptions and will very like not pass with your implementation. But I hope you get the point.

Related

Testing a signup confirmation with Rspec/Factory Girl/Rails

Trying to create an Rspec/Factory girl test to make sure that Devise's confirmation on signup is covered - the site has 3 languages (Japanese, English, Chinese) so I want to make sure nothing breaks the signup process.
I have the following factories:
user.rb << Has everything needed for the general user mailer tests
signup.rb which has:
FactoryGirl.define do
factory :signup do
token "fwoefurklj102939"
email "abcd#ek12o9d.com"
end
end
The devise user_mailer method that I want to test is:
def confirmation_instructions(user, token, opts={})
#user = user
set_language_user_only
mail to: #user.email,
charset: (#user.language == User::LANGUAGE_JA ? 'ISO-2022-JP' : 'UTF8')
end
I cannot for the life of me figure out how to get the token part to work in the test - any advice or ideas?
I have been trying something along these lines (to check the email is being sent) without success:
describe UserMailer, type: :mailer do
describe "sending an email" do
after(:all) { ActionMailer::Base.deliveries.clear }
context "Japanese user emails" do
subject(:signup) { create(:signup) }
subject(:user) { create(:user) }
subject(:mail) do
UserMailer.confirmation_instructions(user, token, opts={})
end
it "sends an email successfully" do
expect { mail.deliver }.to change { ActionMailer::Base.deliveries.size }.by(1)
end
end
end
end
The resulting error is undefined local variable or methodtoken'and I cannot work out why it is not coming from thesignup` factory. I tried changing
subject(:mail) do
UserMailer.confirmation_instructions(user, token, opts={})
end
to
subject(:mail) do
UserMailer.confirmation_instructions(user, signup.token, opts={})
end
but then I received this error:
Failure/Error: subject(:signup) { create(:signup) }
NameError:
uninitialized constant Signup
EDIT: I forgot to mention something important - the actual code all works for user signups in all 3 languages, so I am certain that this is definitely my inexperience with testing at fault.
subject(:mail) do
UserMailer.confirmation_instructions(user, user.confirmation_token)
end
This varies of course depending on what your exact implementation is but your user class should be generating the token:
require 'secure_random'
class User
before_create :generate_confirmation_token!
def generate_confirmation_token!
confirmation_token = SecureRandom.urlsafe_base64
end
end
Creating a separate factory is unnecessary and won't work since FactoryGirl will try to create an instance of Signup which I'm guessing that you don't have.
Factories are not fixtures.

Testing authentication with Sorcery and RSpec

I've spent far too long messing with this before asking for help. I can't seem to get RSpec and Sorcery to play together nicely. I've read through the docs on Integration testing with Sorcery and can post the login action properly, but my tests still doesn't think the user is logged in.
# spec/controllers/user_controller_spec
describe 'user access' do
let (:user) { create(:user) }
before :each do
login_user(user[:email], user[:password])
end
it "should log in the user" do
controller.should be_logged_in
end
end
And my login_user method
# spec/support/sorcery_login
module Sorcery
module TestHelpers
module Rails
def login_user email, password
page.driver.post(sessions_path, { email: email , password: password, remember_me: false })
end
end
end
end
The sessions controller handles the pages properly when I use them on the generated pages just fine. I tried outputting the results of the login_user method and it appears to properly post the data. How do I persist this logged in user through the tests? Does a before :each block not work for this? I'm just not sure where it could be running wrong and I'm pretty new to testing/RSpec so I may be missing something obvious. I'd appreciate any help.
Here's the output of the failed tests:
1) UsersController user access should log in the user
Failure/Error: controller.should be_logged_in
expected logged_in? to return true, got false
I just went through this yesterday. Here's what I did, if it helps.
Sorcery provides a test helper login_user that relies on a #controller object being available. This works great in controller specs, but doesn't work in integration tests. So the workaround in integration tests is to write another method (like the one you have above) to simulate actually logging in via an HTTP request (essentially simulating submitting a form).
So my first thought is that you should try renaming your method to login_user_post or something else that doesn't collide with the built-in test helper.
Another potential gotcha is that it looks to me like the Sorcery helper assumes that your user's password is 'secret'.
Here's a link to the built-in helper so you can see what I'm talking about:
https://github.com/NoamB/sorcery/blob/master/lib/sorcery/test_helpers/rails.rb
Good luck - I really like this gem except for this part. It is really only fully explained by patching together SO posts. Here's the code I use:
Integration Helper
module Sorcery
module TestHelpers
module Rails
def login_user_post(user, password)
page.driver.post(sessions_url, { username: user, password: password})
end
def logout_user_get
page.driver.get(logout_url)
end
end
end
end
Integration Spec (where user needs to be logged in to do stuff)
before(:each) do
#user = create(:user)
login_user_post(#user.username, 'secret')
end
Controller Spec (where the regular login_user helper works fine)
before(:each) do
#user = create(:user)
login_user
end
Note that login_user doesn't need any arguments if you have an #user object with the password 'secret'.
Did you try adding to spec/spec_helpers.
RSpec.configure do |config|
# ...
config.include Sorcery::TestHelpers::Rails::Controller
end
Nota that you need to include Sorcery::TestHelpers::Rails::Controller, not just Sorcery::TestHelpers::Rails.
Then you will be able to login_user from any controller specs like:
describe CategoriesController do
before do
#user = FactoryGirl::create(:user)
end
describe "GET 'index'" do
it "returns http success" do
login_user
get 'index'
expect(response).to be_success
end
end
end
The way you pass a password is probably wrong. It may be encrypted at this point. In provided example I will try to do this at first:
describe 'user access' do
let (:user) { create(:user, password: 'secret') }
before :each do
login_user(user[:email], 'secret')
end
it "should log in the user" do
controller.should be_logged_in
end
end
This seems to be very poorly documented. The above solutions did not work for me. Here's how I got it to work:
Check your sessions_url. Make sure it is correct. Also, check what params are necessary to log in. It may be email, username, etc.
module Sorcery
module TestHelpers
module Rails
def login_user_post(email, password)
page.driver.post(sessions_url, { email:email, password: password })
end
end
end
end
RSpec config:
config.include Sorcery::TestHelpers::Rails
Spec helper:
def app
Capybara.app
end
spec/controllers/protected_resource_spec.rb:
describe UsersController do
before do
# Create user
# Login
response = login_user_post( user.email, :admin_password )
expect( response.headers[ 'location' ]).to eq 'http://test.host/'
# I test for login success here. Failure redirects to /sign_in.
#cookie = response.headers[ 'Set-Cookie' ]
end
specify 'Gets protected resource' do
get protected_resource, {}, { cookie:#cookie }
expect( last_response.status ).to eq 200
end

How to test controller actions that require a current session?

I'm adding more controllers to the admin section of the Padrino but I can't workout how to stub the current user or a session with Factory Girl or Mocha.
What is a good way for testing controller actions that need a current session?
Caveat: I've not used Padrino, and you've not given any code you've tried, so this is quite general and vague.
Alternative 1
Don't stub the session, instead use a testing framework like Capybara that sets up a cookie jar for you. Use an RSpec shared_context with before and after blocks that run the login.
I don't remember the exact syntax for Capybara and I'll leave you to look it up, but it would be something like this:
shared_context "When logged in" do
before do
visit "/login"
fill_in "username", user.name
fill_in "password", user.password
click "login!"
end
after do
# log out…
end
end
describe "Something that you need to be logged in for" do
let(:user) { OpenStruct.new({name: "blah", password: "blurgh" }) }
include "When logged in"
before do
visit "/only/authenticated/see/this"
end
subject { page }
it { should be_ok }
it { #… }
end
Alternative 2
Using Rack::Test, look at this answer
Alternative 3
Here are the authentication helpers, so you should stub logged_in? to return true and current_account to return the user double (whether that's from FactoryGirl or a let or wherever). That way your app won't ask for the information from session.
This solution seem to work
def set_current_user(user)
ApplicationController.stub(:current_user).and_return(user)
session[:identity_id] = user.id
end

How does #user.save work in user_spec.rb? (rails tutorial chapter 6)

I am doing the Ruby on Rails Tutorial and am up to Listing 6.29. It describes the testing of a (seemingly standard) user authentication process.
My problem is understanding the following (edited) part of the user_spec.rb file:
describe User do
before do
#user = User.new(name: "Example User", email: "user#example.com",
password: "foobar", password_confirmation: "foobar")
end
subject { #user }
describe "return value of authenticate method" do
before { #user.save }
let(:found_user) { User.find_by_email(#user.email) }
describe "with valid password" do
it { should == found_user.authenticate(#user.password) }
end
describe "with invalid password" do
let(:user_for_invalid_password) { found_user.authenticate("invalid") }
it { should_not == user_for_invalid_password }
specify { user_for_invalid_password.should be_false }
end
end
end
My main confusion is the line:
before { #user.save }
Does this really save the test user in the database? Won't saving this test user before testing the correctness of its password make the test redundant? It looks to me like I'm merely saving the user (with its password) and then checking if it still has the same password (with which I just saved it!) Is someone able to clarify why I'm mistaken?
Yes, it really does save the user in the database (most likely to be cleaned out by database_cleaner, or something, before the next test - tests are typically meant to be mostly run in isolation from one another and do not usually perpetuate state).
Contrary to making the test redundant, it is a required element. The test in question is for the authenticate method, not user creation. The user is being created in order to test the authenticate method against it. Basically, what is happening here is that it is creating the user, and then trying to authenticate that same user with first a valid password, and next and invalid password, to ensure the proper functionality of the authenticate method.

Factory girl not initiating object

I guess the problem is that I do not know how to use factory girl with Rspec correctly. Or testing in rails correctly for that matter. Still think it is a bit weird though..
I have a class, User, with the following factory:
FactoryGirl.define do
factory :user do
name "admin"
email "admin#admin.com"
adminstatus "1"
password "foobar"
password_confirmation "foobar"
end
factory :user_no_admin, class: User do
name "user"
email "user#user.com"
adminstatus "2"
password "foobar"
password_confirmation "foobar"
end
...
My test looks like this:
...
describe "signin as admin user" do
before { visit login_path }
describe "with valid information" do
let(:user_no_admin) { FactoryGirl.create(:user_no_admin) }
let(:user) { FactoryGirl.create(:user) }
before do
fill_in "User", with: user.name
fill_in "Password", with: user.password
click_button "Login"
end
it "should list users if user is admin" do
response.should have_selector('th', content: 'Name')
response.should have_selector('td', content: user_no_admin.name)
response.should have_selector('td', content: user.name)
end
end
end#signin as admin user
...
Basically I am trying to test that if you log in as an admin, you should see a list of all the users. I have a test for logging on as a non-admin later on in the file. I have a couple of users in the db already.
In the list of users 'admin' that logged in is displayed along with the users already in the db. 'user' is however not displayed unless I do something like this before:
fill_in "User", with: user_no_admin.name
fill_in "Password", with: user_no_admin.password
It is as if it won't exist unless I use it. However, if I use a puts it does print the information I am putting, even if I do not do the 'fill_in' above.
I have a similar example where a puts helps me.
describe "should have company name" do
let(:company) { FactoryGirl.create(:company) }
let(:category) { FactoryGirl.create(:category) }
let(:company_category) { FactoryGirl.create(:company_category, company_id: company.id, category_id: category.id) }
it "should contain companies name" do
puts company_category.category_id
get 'categories/' + company.categories[0].id.to_s
response.should have_selector('h4', :content => company.name)
end
end
Without the puts above I get a
Called id for nil
Do I have to initiate(?) an object created by Factory girl before I can use it in some way?
Any other code needed?
let(:whatever)
Is not creating the objects until the first time you call them. If you want it to be available before first use, use
let!(:whatever)
instead.
Or use a before block:
before(:each) do
#company = FactoryGirl.create(:company)
....
end
Which will create the objects before you need to use them.
Instead of:
factory :user do
name "admin"
email "admin#admin.com"
...
I will do:
factory :user do |f|
f.name "admin"
f.email "admin#admin.com"
...
Instead of:
let(:user_no_admin) { FactoryGirl.create(:user_no_admin) }
let(:user) { FactoryGirl.create(:user) }
I will do:
#user_no_admin = Factory(:user_no_admin)
#user = Factory(:user)
I had a similar issue with an existing test I broke, with a slightly different cause that was interesting.
In this case, the controller under test was originally calling save, but I changed it to call save!, and updated the test accordingly.
The revised test was:
Declaring the instance a let statement
Setting an expectation on the save! method (e.g. expect_any_instance_of(MyObject).to receive(:save!) )
Using the instance for the first time after the expectation.
Internally, it would appear that FactoryGirl was calling the save! method, and after changing the expectation from save to save!, no work was actually done (and the code under test couldn't find the instance from the DB)
that I needed to update and had a hard time getting to actually pass without a hack)
Try to use trait in the factory girl,there is an example as mentioned in the this link

Resources