I have two tests. The results of each depend upon the user count.
At the moment, I have no user fixtures. The second test introduces a user in its setup method. The first test would fail if there were users in the db.
But I want to introduce users in users.yml. As such, the first test will fail because of the existing users. Is there any way I can instruct this test to ignore fixtures/users.yml?
require 'test_helper'
class UsersSignupTestWithoutExistingUsers < ActionDispatch::IntegrationTest
test "Signup page is accessible" do
get new_user_registration_path
assert_response :success
end
end
class UsersSignupTestWithExistingUsers < ActionDispatch::IntegrationTest
def setup
post user_registration_path, params: {user: {
email: "user#test.com",
password: "password",
password_confirmation: "password"
}}
end
test "Signup page will redirect" do
get new_user_registration_path
assert_response :redirect
end
end
In the test that requires no users in the DB you can stub the method that looks in the DB for users so that it returns an empty set for that test.
Related
Summary
I am building a Rails app which includes a user registration process. A username and password are necessary to create a user object in the database; the username must be unique. I am looking for the right way to test that the uniqueness validation prompts a particular action of a controller method, namely UsersController#create.
Context
The user model includes the relevant validation:
# app/models/user.rb
#
# username :string not null
# ...
class User < ApplicationRecord
validates :username, presence: true
# ... more validations, class methods, and instance methods
end
Moreover, the spec file for the User model tests this validation using shoulda-matchers:
# spec/models/user_spec.rb
RSpec.describe User, type: :model do
it { should validate_uniqueness_of(:username)}
# ... more model tests
end
The method UsersController#create is defined as follows:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
#user = User.new(user_params)
if #user.save
render :show
else
flash[:errors] = #user.errors.full_messages
redirect_to new_user_url
end
end
# ... more controller methods
end
Since the User spec for username uniqueness passes, I know that a POST request which contains a username already in the database will cause UsersController#create to enter the else portion of the conditional, and I want a test to verify this situation.
Currently, I test how UsersController#create handles the uniqueness validation on username in the following manner:
# spec/controllers/users_controller_spec.rb
require 'rails_helper'
RSpec.describe UsersController, type: :controller do
describe 'POST #create' do
context "username exists in db" do
before(:all) do
User.create!(username: 'jarmo', password: 'good_password')
end
before(:each) do
post :create, params: { user: { username: 'jarmo', password: 'better_password' }}
end
after(:all) do
User.last.destroy
end
it "redirects to new_user_url" do
expect(response).to redirect_to new_user_url
end
it "sets flash errors" do
should set_flash[:errors]
end
end
# ... more controller tests
end
Issue
My primary concern is the before and after hooks. Without User.last.destroy, this test will fail when run in the future: The new record can't be created, and thus the creation of a second record with the same username doesn't occur.
Question
Should I be testing this particular model validation in the controller spec? If so, are these hooks the right/best way to accomplish this goal?
I'll steer away from an opinion on the 'should I...' part, but there are a couple of aspects worth considering. First, although controller tests have not been formally deprecated, they have generally been discouraged by both the Rails and Rspec teams for a while now. From the RSpec 3.5 release notes:
The official recommendation of the Rails team and the RSpec core team
is to write request specs instead. Request specs allow you to focus on
a single controller action, but unlike controller tests involve the
router, the middleware stack, and both rack requests and responses.
This adds realism to the test that you are writing, and helps avoid
many of the issues that are common in controller specs.
Whether or not the scenario warrants a corresponding request spec is a judgement call, but if you want to unit test the validation at the model level, check out the shoulda matchers gem, which assists with model validation testing).
In terms of your question about hooks, before(:all) hooks run outside a database transaction, so even if you have use_transactional_fixtures set to true in your RSpec configuration, they won't be automatically rolled back. So, a matching after(:all) like you have is the way to go. Alternatives include:
Creating the user inside a before(:each) hook, which does run in a transaction and is rolled back. That's at the potential cost of some test performance.
Use a tool like the Database Cleaner gem, which gives you fine-grained control over the strategies for cleaning your test databases.
If you want to cover the controller together with the user feedback aspect of this I would suggest a feature spec:
RSpec.feature "User creation" do
context "with duplicate emails" do
let!(:user) { User.create!(username: 'jarmo', password: 'good_password') }
it "does not allow duplicate emails" do
visit new_user_path
fill_in 'Email', with: user.email
fill_in 'Password', with: 'p4ssw0rd'
fill_in 'Password Confirmation', with: 'p4ssw0rd'
expect do
click_button 'Sign up'
end.to_not change(User, :count)
expect(page).to have_content 'Email has already been taken'
end
end
end
Instead of poking inside the controller this drives the full stack from the user story and tests that the view actually has an output for the validation errors as well - it thus provides value where a controller spec provides very little value.
Use let/let! to setup givens for a particular example as it has the advantage that you can reference them in the example through the helper method it generates. before(:all) should generally be avoided apart from stuff like stubbing out API's. Each example should have its own setup/teardown.
But you also need to deal with the fact that the controller itself is broken. It should read:
class UsersController < ApplicationController
def create
#user = User.new(user_params)
if #user.save
redirect_to #user
else
render :new, status: :unprocessable_entity
end
end
end
When a record is invalid you should NOT redirect back. Render the form again as you're displaying the result of performing a POST request. Redirecting back will make for a horrible user experience since all the fields will be blanked out.
When creating a resource is successful you should redirect the user to the newly created resource so that the browser URL actually points to the new resource. If you don't reloading the page will load the index instead.
This also removes the need to stuff the error messages in the session. If you want to give useful feedback through the flash you would do it like so:
class UsersController < ApplicationController
def create
#user = User.new(user_params)
if #user.save
redirect_to #user
else
flash.now[:error] = "Signup failed."
render :new, status: :unprocessable_entity
end
end
end
And you can test it with:
expect(page).to have_content "Signup failed."
I'm trying to ensure proper user access is maintained with Devise in Rails 4, and I'm having a hard time logging a user in in the test suite.
The simplest case:
require 'test_helper'
include Devise::TestHelpers
class SiteLayoutTest < ActionDispatch::IntegrationTest
def setup
#user = users(:test1)
end
test "logged in should get index" do
sign_in #user
get users_path
assert_response :success
assert_select "title", "User Index"
end
end
So far I've not done more really than just implement Devise and a Users controller with the appropriate actions.
I consistently get: NoMethodError: undefined method 'env' for nil:NilClass, referring specifically to the line containing sign_in #user and I can find other instances of people getting the same error, but never seem to find an actual solution to the problem I'm attempting to solve.
How do I log a user in with Devise in Rails 4 for testing purposes? Thanks.
EDIT:
fixtures/users.yml
test1:
id: '1'
email: 'test1#example.com'
encrypted_password: <%= Devise::Encryptor.digest(User, "password") %>
created_at: <%= Time.now - 6.minutes %>
updated_at: <%= Time.now - 4.minutes %>
SOLUTION IN SITU:
test "logged in should get index" do
post user_session_path, 'user[email]' => #user.email, 'user[password]' => 'password'
get users_path
assert_response :success
assert_select "title", "User Index"
end
This is from their docs:
"Do not use Devise::TestHelpers in integration tests."
You have to sign in manually. This is an example of a test for a website that does not allow users to get to the root path unless signed in. You can create a method in a support file that signs in the user manually and then call it whenever you want to sign in the user, so that you don't have to use this code every time you need to sign in a user.
require 'test_helper'
class UserFlowsTest < ActionDispatch::IntegrationTest
test "signed in user is redirected to root_path" do
get user_session_path
assert_equal 200, status
#david = User.create(email: "david#mail.com", password: Devise::Encryptor.digest(User, "helloworld"))
post user_session_path, 'user[email]' => #david.email, 'user[password]' => #david.password
follow_redirect!
assert_equal 200, status
assert_equal "/", path
end
test "user is redirected to sign in page when visiting home page" do
get "/"
assert_equal 302, status
follow_redirect!
assert_equal "/users/sign_in", path
assert_equal 200, status
end
end
EDIT: Just in case it's helpful in the future. You can use Warden Test Helpers for integration tests but the way above is a better test. This is a working example:
require 'test_helper'
include Warden::Test::Helpers
class UserFlowsTest < ActionDispatch::IntegrationTest
test "user can see home page after login" do
#david = User.create(email: "david#mail.com", password: Devise::Encryptor.digest(User, "helloworld"))
login_as(#david)
get "/"
assert_equal 200, status # User gets root_path because he loged in
assert_equal "/", path
logout
get "/"
assert_equal 302, status # User is redirected because he loged out
Warden.test_reset! #reset Warden after each example
end
end
I'm using devise for authentication in a small web application, and I'm having a few problems writing some integration tests.
The tests are going to be simple, such as
login works with valid credentials
login rejected with invalid credentials
...
using the techniques that were described in the rails tutorial, but rather than against a home grown authentication system, I'm attempting to retrofit it against devise.
I can use the sign_in function without any problems, and I'm doing that in one or two of my controller tests, e.g.
require 'test_helper'
class mySimpleControllerTest < ActionController::TestCase
include Devise::TestHelpers
def setup
#user = User.create!(
:firstname => "ANOther",
:surname => 'Person',
:username=> 'aperson',
:email => 'aperson#example.com',
:password => 'pass123',
:password_confirmation => 'pass123'
)
sign_in #user
end
test "should get home" do
get :home
assert_response :success
assert_select "title", "TEST PAGE"
end
end
that works wonderfully well. The problem I have is my integration tests for testing the login functionality. I don't want both setup and teardown functions in there, since some tests will have to check against logged out behaviour, some against logged in, and others against reset password etc.
the following test is always is responding with invalid username and password, even though the passwords are correct. Eventually, I want this test to pass when the username or password is incorrect, but right now it responds this way regardless of whether it is or it isn't.
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
test "should be redirected if root_path is called when logged out" do
get root_path
assert_response :redirect
end
test "login with invalid information" do
get new_user_session_path
assert_template 'devise/sessions/new'
post user_session_path, 'user[email]' => 'aperson#example.com', 'user[password]' => 'pass123'
...
I'm assuming the reason for this is because my test database doesn't contain a real user, in fact, the users table is currently empty. Which makes sense, since no user, it should respond with invalid username or password. However, if that is the case, how can I guarantee that the test database is populated with this default user when calling rake test?
After attempting several ways of getting this to work, I eventually moved all tests over to RSpec and Capybara, which will allow me to post data to forms.
I did test whether or not there was an issue with persistent users and this seemed to not be the case. The problem seemed to be that
post user_session_path, 'user[email]' => ...
didn't seem to actually post anything.
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.
I'm searching for a solution for a weird problem. I have a controller, that needs authentication (with the devise gem). I added the Devise TestHelpers but i can't get it working.
require 'test_helper'
class KeysControllerTest < ActionController::TestCase
include Devise::TestHelpers
fixtures :keys
def setup
#user = User.create!(
:email => 'testuser#demomailtest.com',
:password => 'MyTestingPassword',
:password_confirmation => 'MyTestingPassword'
)
sign_in #user
#key = keys(:one)
end
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:keys)
end
test "should get new" do
get :new
assert_response :success
end
test "should create key" do
assert_difference('Key.count') do
post :create, :key => #key.attributes
end
assert_redirected_to key_path(assigns(:key))
end
test "should destroy key" do
assert_difference('Key.count', -1) do
delete :destroy, :id => #key.to_param
end
assert_redirected_to keys_path
end
end
And i get the following output in my "rake test" window:
29) Failure:
test_should_create_key(KeysControllerTest) [/test/functional/keys_controller_test.rb:29]:
"Key.count" didn't change by 1.
<3> expected but was
<2>.
30) Failure:
test_should_destroy_key(KeysControllerTest) [/test/functional/keys_controller_test.rb:37]:
"Key.count" didn't change by -1.
<1> expected but was
<2>.
31) Failure:
test_should_get_index(KeysControllerTest) [/test/functional/keys_controller_test.rb:19]:
Expected response to be a <:success>, but was <302>
32) Failure:
test_should_get_new(KeysControllerTest) [/test/functional/keys_controller_test.rb:25]:
Expected response to be a <:success>, but was <302>
Can someone tell my, why devise doesn't authenticate? I'm using the exact same procedure for an AdminController and it works perfect.
Are you using Devise with confirmable? In this case, create is not enough and you need to confirm the user with #user.confirm!
Second, why do you create the user in the functional test? Declare your users in the fixture like this (confirmed_at if you require confirmation only):
test/fixtures/users.yml:
user1:
id: 1
email: user1#test.eu
encrypted_password: abcdef1
password_salt: efvfvffdv
confirmed_at: <%= Time.now %>
and sign them in in your functional tests with:
sign_in users(:user1)
Edit: I just saw, that in my app the Devise-Testhelpers are declared in test/test-helpers.rb and I don't know if this makes a difference, maybe you want to try:
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActionController::TestCase
include Devise::TestHelpers
end
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
# -- they do not yet inherit this setting
fixtures :all
# Add more helper methods to be used by all tests here...
end
This took me some time to figure out but it turns out the answer is really simple.
The point is that, in your fixtures file (users.yml), you need to make sure the user is 'confirmed' (assuming that you specified "confirmable" in your User model). So, for instance, put this in your users.yml:
user_one:
confirmed_at: 2015/01/01
That's all, no need to specify other fields (email, encrypted password, etc).
Now in your controller test (e.g. in 'setup' or in 'before') you simply code:
sign_in users(:user_one)
And then it should just work!
I had a similar issue (but using FactoryGirl, rather than Fixtures) and was able to resolve it by simply using FactoryGirl.create(:user) rather than FactoryGirl.build(:user).
Evidently, Devise requires the user to have been persisted to the Database, for everything to work properly.