I have written a functional test that changes some of the request object's environment variables to simulate a user has logged in.
require 'test_helper'
class BeesControllerTest < ActionController::TestCase
# See that the index page gets called correctly.
def test_get_index
#request.env['HTTPS'] = "on"
#request.env['SERVER_NAME'] = "sandbox.example.com"
#request.env['REMOTE_USER'] = "joeuser" # Authn/Authz done via REMOTE_USER
get :index
assert_response :success
assert_not_nil(assigns(:bees))
assert_select "title", "Bees and Honey"
end
end
The functional test works fine.
Now I want to do something similar as part of integration testing. Here is what I tried:
require 'test_helper'
class CreateBeeTest < ActionController::IntegrationTest
fixtures :bees
def test_create
#request.env['HTTPS'] = "on"
#request.env['SERVER_NAME'] = "sandbox.example.com"
#request.env['REMOTE_USER'] = "joeuser" # Authn/Authz done via REMOTE_USER
https?
get "/"
assert_response :success
[... more ...]
end
end
I get an error complaining that #request is nil. I suspect this has something to do with the session object, but I am not sure how to get it to work.
You can set HTTPS in integration tests with
https!
And set the host name with:
host! "sandbox.example.com"
Which may be equivalent to what you want to do?
This is described in the Rails guides Rails guides
You can change request variables via parameters to post method.
For your case, method test_create will be:
def test_create
https!
get "/", nil, { 'SERVER_NAME'] => "sandbox.example.com", 'REMOTE_USER'] => "joeuser" }
assert_response :success
[... more ...]
end
Same works for setting post request to raw data:
post root_path, nil, { 'RAW_POST_DATA' => 'some string' }
Related
I'm currently writing tests for my Rails application using the default Minitest framework and it's working quite fine. The only problem I have that my configuration initializer is run (I guess) every test twice and I can't figure out why this happens.
So, I have an initializer which sets some config parameters from the database for my application:
# config/initializers/load_application_settings.rb
Rails.configuration.after_initialize do
if defined?(::Rails::Server) && !Rails.env.test?
WebMenueNew::Application.config.header_title = "WebMenue | #{Configuration.first.clinicName}"
WebMenueNew::Application.config.clinic_name = Configuration.first.clinicName
WebMenueNew::Application.config.clinic_location = Configuration.first.city
WebMenueNew::Application.config.proxy_server = Configuration.first.request_server
WebMenueNew::Application.config.order_time = Configuration.first.orderTime.strftime('%H:%M')
WebMenueNew::Application.config.repeating_period = Configuration.first.repeatMealTime
WebMenueNew::Application.config.clinic_iban = Configuration.first.iban
WebMenueNew::Application.config.clinic_bic = Configuration.first.bic
WebMenueNew::Application.config.clinic_bank = Configuration.first.bank
end
end
I tried to wrap it witht he if !Rails.env.test? condition, but it doesn't seem to work. In my controller test, I tried to initialize the value in the setup method and destroy all params in the teardown method:
require 'test_helper'
class SessionsControllerTest < ActionDispatch::IntegrationTest
setup do
Rails.configuration.clinic_name = "BDH-Klinik Braunfels"
#user = users(:one)
end
test "should get new" do
get root_path
assert_response :success
assert_select "title", "WebMenue | BDH-Klinik Braunfels"
end
test "should be valid external login" do
post login_path, params: { session: { using_ldap: false, username: #user.email, password: "valid_password" } }
assert_response :redirect
assert_redirected_to #user
end
teardown do
Configuration.destroy_all
end
end
Even if i move it out of the setup method and into the test method for the new action, it always produces two record in the test database and I get the following error:
Minitest::UnexpectedError: ActiveRecord::RecordNotUnique:
PG::UniqueViolation: ERROR: duplicate key value violates unique
constraint "configurations_pkey"
DETAIL: Key (id)=(980190962) already exists.
In my new view for the sessions controller I set the title of the application dynamically, that's why I need the parameter:
<% content_for(:title) { "WebMenue | #{Rails.configuration.clinic_name}" } %>
Is there any workaround so that the initializer is not run on test suites? Or am I missing something? I'm relatively new to testing in Rails so don't be too hard :).
When doing functional tests for controllers in rails how can I provide dynamic application instance variables to my test which live in the request.
I have a #station object that is initialized in a before action in my application controller (available in all other controllers), however the #station object is defined by the domain entry of the user e.g.: blue.mydomain.com. So it could have 3 different flavors, and the controller actions params[:id] are only valid for a certain flavor.
Further if I don't give my #station a flavor for my test environment it will fail utterly:
(Here code from a helper that gets called in a before action in my application_controller.rb)
def init_station
if Rails.env == "test"
#station=Station.new('blue')
else
#station=Station.new(domain_flavor_picker)
end
end
ApplicationController
....
before_action :init_station
.....
end
Thus I can only test for 'blue' or switch the flavor in my before action and then mock for different id!
test:
describe MyController do
before do
#id="10215d8da3f4f278cec747f09985b5528ec9e"
end
it "should get index action" do
p assigns(:station) # is nil
get :artist_biography, id: #id, locale: I18n.available_locales.sample
assert_response :success
assert_not_nil assigns(:meta)
assert_not_nil assigns(:nav)
assert_not_nil assigns(:content)
end
end
As you can see I am also in need of providing a locale variable. I managed to mix up that call with I18n.available_locales.sample
How can I dynamically switch or manipulate my #station instance variable?
My issue was that I needed to provide minitest with an initial host! From #smathy answer I knew that I needed a Mock Request for the Controller!
Turns out that it is quite easy to set it in MiniTest if you know how!
Rails provides an ActionDispatch::TestRequest object which in itself seems to be a Rack::MockRequest object:
DEFAULT_ENV = Rack::MockRequest.env_for('/', 'HTTP_HOST' => 'test.host', 'REMOTE_ADDR' => '0.0.0.0', 'HTTP_USER_AGENT' => 'Rails Testing' )
So all I had to do in my test was:
before do
#request.env['HTTP_HOST'] = %w(blue.mydomain.com red.mydomain.com green.mydomain.com).sample
end
to initialize my #station object with a sample of flavored domains.
assigns :station will only return a value after you do the request, ie. after the get line. Until you've done the request none of your controller code has been run for that test.
You also shouldn't use #vars in rspec, use let instead, and a few other things that I've shown below, many of which I learned from BetterSpecs
The Crux of your Issue
Assuming that domain_flavor_picker is a method in your controller then you should just mock that so you can different tests for the different return values of it. So, this shows the context for one of the return values of domain_flavor_picker, but you'd add other contexts for other values:
describe MyController do
let(:id) { "10215d8da3f4f278cec747f09985b5528ec9e" }
describe "GET /artist_biography" do
context "when domain_flavor is blue" do
before do
allow(controller).to receive(:domain_flavor_picker) { "blue" } # <-- MOCK!
end
context "when valid id" do
before { get :artist_biography, id: id, locale: I18n.available_locales.sample }
subject { response }
it { is_expected.to be_success }
it "should assign :meta" do
expect(assigns :meta).to be_present # better to have an actual matcher here
end
it "should assign :nav" do
expect(assigns :nav).to be_present # ditto
end
it "should assign :content" do
expect(assigns :content).to be_present # ditto
end
end
end
end
end
In my routes file, I have this random route without a controller
match "/ping" => lambda{ |env| [200, {'Content-Type' => 'text/plain'}, ['ACK']] }
Using Test::Unit / MiniTest, how would I go about testing, that yes the route /ping return 'ACK'
When you create a route without a controller, you cannot use ActionController::TestCase to test it. Instead, you should use ActionDispatch::IntegrationTest. Create a file at test/integration/ping_test.rb that includes the following:
require "test_helper"
class PingTest < ActionDispatch::IntegrationTest
def test_ping
get "/ping"
assert_response :success
assert_equal "ACK", response.body
end
end
Definitely using integration tests. See details here: http://guides.rubyonrails.org/testing.html#integration-testing
I'm trying to get RSpec working for a simple scaffolded app, starting with the rspec scaffold tests.
Per the devise wiki, I have added the various devise config entries, a factory for a user and an admin, and the first things I do in my spec controller is login_admin.
Weirdest thing, though... all my specs fail UNLESS I add the following statement right after the it ... do line:
dummy=subject.current_user.inspect
(With the line, as shown below, the specs pass. Without that line, all tests fail with the assigns being nil instead of the expected value. I only happened to discover that when I was putting some puts statements to see if the current_user was being set correctly.)
So what it acts like is that dummy statement somehow 'forces' the current_user to be loaded or refreshed or recognized.
Can anyone explain what's going on, and what I should be doing differently so I don't need the dummy statement?
#specs/controllers/brokers_controller_spec.rb
describe BrokersController do
login_admin
def valid_attributes
{:name => "Bill", :email => "rspec_broker#example.com", :company => "Example Inc", :community_id => 1}
end
def valid_session
{}
end
describe "GET index" do
it "assigns all brokers as #brokers" do
dummy=subject.current_user.inspect # ALL SPECS FAIL WITHOUT THIS LINE!
broker = Broker.create! valid_attributes
get :index, {}, valid_session
assigns(:brokers).should eq([broker])
end
end
describe "GET show" do
it "assigns the requested broker as #broker" do
dummy=subject.current_user.inspect # ALL SPECS FAIL WITHOUT THIS LINE!
broker = Broker.create! valid_attributes
get :show, {:id => broker.to_param}, valid_session
assigns(:broker).should eq(broker)
end
end
and per the devise wiki here is how I login a :user or :admin
#spec/support/controller_macros.rb
module ControllerMacros
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in Factory.create(:admin) # Using factory girl as an example
end
end
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = Factory.create(:user)
user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the confirmable module
sign_in user
end
end
end
What a struggle! Thank you Robin, I've been googling on this for hours and finally saw your post; now my controller tests are working :)
To add to your answer, I figured out how to get the devise session into the valid_session hash, which allows the controller tests to run properly as generated by rails.
def valid_session
{"warden.user.user.key" => session["warden.user.user.key"]}
end
In your tests, there is the following code:
def valid_session
{}
end
...
get :index, {}, valid_session
Because of this 'session' variable, the "log_in" that you did is essentially not being used during the 'get'.
The way that I solved it was to remove all of the "valid_session" arguments to the get, post, put, delete calls in that controller's spec. The example above becomes:
get :index, {}
I suspect that there's a way to add the devise's session to the "valid_session" hash, but I don't know what it is.
Thanks for this solution.
If you are using a different Devise model, the session id also changes.
For a model Administrator use the following:
def valid_session
{'warden.user.administrator.key' => session['warden.user.administrator.key']}
end
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.