Is there a better way to test this Rails controller with RSpec? - ruby-on-rails

I have an Account model that validates that its subdomain is unique.
I'm trying to learn how to test controllers with RSpec.
Here's what I've come up with, but it's quite different than the generated RSpec test and i'm wondering if this is a good way to test this or if there's a better way.
My test:
describe "POST create" do
describe "with valid params" do
it "creates a new Account" do
original_count = Account.count
account = FactoryGirl.build(:account, :subdomain => 'newdomain')
post :create, {:account => account}
account.save!
new_count = Account.count
expect(new_count).to eq(original_count + 1)
end
...
EDIT
I forgot to point out the fact that in my spec_helper I have the code below. It is needed because of the way i'm handling subdomains:
config.before(:each, :type => :controller) do
#account = FactoryGirl.create(:account)
#user = FactoryGirl.create(:user)
#request.host = "#{#account.subdomain}.example.com"
sign_in #user
end

There is, by using Rspec's expect to change, and FactoryGirl's attributes_for (might need tweaking, not tested):
describe "POST create" do
describe "with valid params" do
it "creates a new Account" do
expect{
post :create, { account: attributes_for(:account) }
}.to change{Account.count}.by(1)
end
...
Validate your unique subdomain constraint in your unit tests, perhaps using shoulda-matchers:
describe Account do
it { should validate_uniqueness_of(:subdomain) }
end

I would make a controller spec a true unit test and not involve the database. Something like:
describe AccountsController do
describe '#create' do
it "creates a new Account" do
account_attrs = FactoryGirl.attributes_for :account
expect(Account).to receive(:create!).with account_attrs
post :create, account: account_attrs
end
end
end
I'd also have a feature spec (or a Cucumber scenario) that integration-tested the entire interaction of which the post to AccountsController was a part. Actually if I had a happy-path feature spec/scenario there would be no need to write the controller spec above, but I'd need controller specs for error paths (like trying to create an Account with the same subdomain as an existing Account) and other variations and I'd write them similarly to the spec above, by stubbing and mocking database calls.

Related

Authenticated RSpec testing and Factory Girl

I'm writing basic tests for a simple CRUD rails application. I am using devise for authentication and Factory Girl for object creation whilst testing, after testing I am clearing the test.db with the Database Cleaner gem.
I am testing a controller with RSpec but need an admin user to be signed in for this to be a 'true' test.
So far I have been following documentation but I don't believe it is working.
I have a test which checks if the count has been changed by one:
describe "POST create" do
context "with valid attributes" do
it "creates a new room" do
expect{ post :create, room: FactoryGirl.attributes_for(:room) }.to change(Room,:count).by(1)
end
When I run the test suite I get the error:
expected #count to have changed by 1, but was changed by 0
From reading around it seems I need to setup authentication with my tests. To do this I have created relevant Factories:
# Devise User Class
factory :user do
email "basicuser#mvmanor.co.uk"
password "u"
password_confirmation "u"
admin false
customer false
end
# Admin
factory :admin, class: User do
email "basicadmin#mvmanor.co.uk"
password "a"
password_confirmation "a"
admin true
customer false
end
I have also created the relevant Devise mappings macros:
module UserControllerMacros
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in FactoryGirl.create(:admin) # Using factory girl as an example
end
end
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
sign_in FactoryGirl.create(:user) # Using factory girl as an example
end
end
end
I'm not sure if I'm heading in the right direction with this. I certainly need my controller tests to be authenticated.
Before the first describe statement add this:
let!(:admin) { FactoryGirl.create(:admin) }
before { subject.stub(current_user: admin, authenticate_user!: true) }
it should stub your authentication.
And one little trick for your happiness: add in your spec_helper.rb anywhere within
RSpec.configure do |config|
...
end
block this code:
config.include FactoryGirl::Syntax::Methods
and now you don't need to preface all factory_girl methods with FactoryGirl, so instead of FactoryGirl.create you can write just create.

Rails Controller testing

I am doing the thoughtbot intro to testing program. Im not sure how to test for what they want.
Below is my test.
require "rails_helper"
describe PeopleController do
describe "#create" do
context "when person is valid" do
it "redirects to #show" do
post :create, FactoryGirl.build_stubbed(:person)
expect(response).to redirect_to(show_people_path)
end
end
context "when person is invalid" do
it "redirects to #new" do
pending "create this test"
end
end
end
end
I am of course using factory girl. I have tried several methods. I really don't know hoe to test this controller.
Any insights would be great.
I would create an 'invalid' person using the FactoryGirl, and send it as a parameter to the post :create.
To create an invalid person record, why don't you use nested factories in FactoryGirl? Depending on the validation in your model, you can simply do something like:
spec/factories/person.rb
FactoryGirl.define do
factory :person do
...
factory :invalid_person do
...
email nil
...
end
end
end
in your test
context "when person is invalid" do
it "redirects to #new" do
post :create, FactoryGirl.build_stubbed(:invalid_person)
expect(response).to redirect_to action: :new
end
end

Mocking and stubbing in testing

I've recently learned how to stub in rspec and found that some benefits of it are we can decouple the code (eg. controller and model), more efficient test execution (eg. stubbing database call).
However I figured that if we stub, the code can be tightly tied to a particular implementation which therefore sacrifice the way we refactor the code later.
Example:
UsersController
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
User.create(name: params[:name])
end
end
Controller spec
# /spec/controllers/users_controller_spec.rb
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
expect(User).to receive(:create)
post :create, :name => "abc"
end
end
end
By doing that didn't I just limit the implementation to only using User.create? So later if I change the code my test will fail even though the purpose of both code is the same which is to save the new user to database
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
#user = User.new
#user.name = params[:name]
#user.save!
end
end
Whereas if I test the controller without stubbing, I can create a real record and later check against the record in the database. As long as the controller is able to save the user Like so
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
post :create, :name => "abc"
user = User.first
expect(user.name).to eql("abc")
end
end
end
Really sorry if the codes don't look right or have errors, I didn't check the code but you get my point.
So my question is, can we mock/stub without having to be tied to a particular implementation? If so, would you please throw me an example in rspec
You should use mocking and stubbing to simulate services external to the code, which it uses, but you are not interested in them running in your test.
For example, say your code is using the twitter gem:
status = client.status(my_client)
In your test, you don't really want your code to go to twitter API and get your bogus client's status! Instead you stub that method:
expect(client).to receive(:status).with(my_client).and_return("this is my status!")
Now you can safely check your code, with deterministic, short running results!
This is one use case where stubs and mocks are useful, there are more. Of course, like any other tool, they may be abused, and cause pain later on.
Internally create calls save and new
def create(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create(attr, options, &block) }
else
object = new(attributes, options, &block)
object.save
object
end
end
So possibly your second test would cover both cases.
It is not straight forward to write tests which are implementation independent. That's why integration tests have a lot of value and are better suited than unit tests for testing the behavior of the application.
In the code you're presented, you're not exactly mocking or stubbing. Let's take a look at the first spec:
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
expect(User).to receive(:create)
post :create, :name => "abc"
end
end
end
Here, you're testing that User received the 'create' message. You're right that there's something wrong with this test because it's going to break if you change the implementation of the controllers 'create' action, which defeats the purpose of testing. Tests should be flexible to change and not a hinderance.
What you want to do is not test implementation, but side effects. What is the controller 'create' action supposed to do? It's supposed to create a user. Here's how I would test it
# /spec/controllers/users_controller_spec.rb
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
expect { post :create, name: 'abc' }.to change(User, :count).by(1)
end
end
end
As for mocking and stubbing, I try to stay away from too much stubbing. I think it's super useful when you're trying to test conditionals. Here's an example:
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
user = User.new(user_params)
if user.save
flash[:success] = 'User created'
redirect_to root_path
else
flash[:error] = 'Something went wrong'
render 'new'
end
end
# /spec/controllers/users_controller_spec.rb
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it "renders new if didn't save" do
User.any_instance.stub(:save).and_return(false)
post :create, name: 'abc'
expect(response).to render_template('new')
end
end
end
Here I'm stubbing out 'save' and returning 'false' so I can test what's supposed to happen if the user fails to save.
Also, the other answers were correct in saying that you want to stub out external services so you don't call on their API every time you're running your test suite.

How to mock a service object? should I mock it?

I have a service object called ResetPassword that handles all the logic for the ResetPassword Controller create action. I also have already tested the service object. Should I mock the service object? I figure I should since it's ready tested and it would cut down on running specs. My test code so far for the controller is below. Not sure if it should be written this way.
require 'spec_helper'
describe ResetPasswordController do
describe "POST create" do
context "when email matches a user" do
let(:user) { Fabricate(:user) }
it "calls password_reset on PasswordReset" do
ResetPassword.stub(:reset_password)
ResetPassword.any_instance.should_receive(:reset_password)
post :create, email: user.email
end
it "redirects to root path" do
post :create, email: user.email
expect(response).to redirect_to root_path
end
end
context "when email doesn't match a user" do
it "redirects to new"
it "displays a flash error"
end
end
end
I think you should mock the service in your controller, but mock it by injecting a mock instead of stubbing on the class or any_instance
Your controller could look like this
class ResetPasswordController < ApplicationController
def create
reset_password_service.reset_password(params[:email])
end
def reset_password_service
#reset_password_service ||= ResetPassword.new
end
def reset_password_service=(val)
#reset_password_service = val
end
end
Then in your spec you can
before { controller.reset_password_service = password_service }
let(:password_service) { double("PasswordService", reset_password: nil) }
it "does something good" do
post :create, email: "foo"
expect(password_service).to have_received(:reset_password).with("foo")
end
Or even better, use an instance_double instead. That will also check that the stubbed methods actually exists on the stubbed class. This is available from RSpec 3.0.0.beta*
let(:password_service) { instance_double(PasswordService, reset_password: nil) }
you can use mockito to mock your service and imockit multiple services with mockito.

RSpec: testing cancan with home-spun authentication

This is a simple question, it's just that I'm still wrapping my head around RSpec's syntax & methodology...so I'm a bit confused. Please bear with me..I'm wondering how to approach testing controllers (and requests, etc.) when Cancan is involved and users can have different levels of authorization.
I've read that one advantage to Cancan is that it keeps all Auth in one model...great! but it seems to have confused testing for me...so I'm looking for advice.
Background: I'm using a bitmask to hold the authorization level for the user (a la Railscast #189). So the integer column roles_mask will hold all the auth for a user. Not sure this matters but now you know.
I'd like to have some tests as a guest user that should fail if guest tries to create a Post. But then in same posts_controller_spec I'd like to test that an admin and moderator can create the same post. Just not sure how best to set this up.
So, should I have a factory for a guest user, one for an admin, etc... Moving #user.roles_mask = 1 to a describe block before specs I want for an "admin" user doesn't work. No method errors. But, if I assign the roles_mask in the Factory it does work?!? So how can I test for varying levels of roles_mask? (nil, 0, 1, 2, 4, etc?)
describe PostsController do
before(:each) do
#user = Factory(:user)
session[:user_id] = #user.id
#attr = Factory.attributes_for(:post)
end
describe "GET index" do
it "assigns all posts as #posts" do
post = #user.posts.create(#attr)
get :index
assigns(:posts).should eq([post])
end
end
...
describe "POST create" do
describe "with valid params" do
it "creates a new Post" do
expect {
post :create, :post => #attr
}.to change(Post, :count).by(1)
end
...
end
# posts_controller.rb
class PostsController < ApplicationController
before_filter :login_required, :except => [:index, :show]
load_and_authorize_resource
...
end
Factory:
Factory.define :user do |user|
user.sequence(:email) { |n| "foo#{n}#dummycorp.com" }
user.password "foobar"
user.password_confirmation { |u| u.password }
user.firstname "foo"
user.roles_mask 1 # < --- remove & tests fail
# < --- how to manipulate dynamically in tests
end
I think you are confused (as I was) by the fact that the controller is protecting access to resources - but it's still the ability class that determines whether the controller's actions can be carried out on a particular resource. Whether you access the resources through the PostsController or some other controller, should really make no difference - you still don't want guests making posts. If you really want to be certain that load_and_authorize_resource is being called in the PostsController you could set up a single functional test for that (guest create post should fail) - then the ability tests should confirm the detail.
require "cancan/matchers"
describe Ability do
before(:each) do
end
[Contact, Question, Provider, Organisation].each do |model|
it "should allow any user to read a #{model.to_s} details but not delete them" do
user= Factory(:user)
ability = Ability.new(user)
ability.should be_able_to(:show, model.new)
ability.should_not be_able_to(:delete, Factory(model.to_s.underscore.to_sym))
end
end
[Contact, Organisation].each do |model|
it "should allow any admin user to delete a #{model.to_s} " do
user= Factory(:admin)
ability = Ability.new(user)
ability.should be_able_to(:delete, Factory.build(model.to_s.underscore.to_sym))
end
end
etc
In my case I have admin users and normal users with different abilities - I just created one of each in turn, and called the relevant methods on the models I wanted to restrict access to. I didn't actually test the controller much at all because load_and_authorize_resource is called in my case in the application_controller and so a single test makes sure it applies throughout the app

Resources