I have a subscribers section in my application with several controllers which inherited from Subscribers::BaseController
Subscribers::BaseController has a before_action :require_subscription
class Subscribers::BaseController < ApplicationController
before_action :require_subscription, if: :require_subscription?
private
def require_subscription
RequireSubscriptionService.call(current_user)
end
def require_subscription?
# several conditions
end
end
And other controllers for example
class Subscribers::EventsController < Subscribers::BaseController
skip_before_action :require_subscription, only: :other_action
def index
end
def show
end
def other_action
end
end
What is the best way to test, that before_action :require_subscription will be called if it necessary in each action with MiniTest
Usually I use Rspec, and I can use expect matcher in this case
For example
describe 'Subscribers::EventsController' do
describe '.index' do
expect_any_instance_of(described_class).to receive(:require_subscription).and_return(:return_value)
subject
end
describe '.other_action' do
expect_any_instance_of(described_class).not_to receive(:require_subscription)
subject
end
end
But how to test this stuff with MiniTest?
My RSpec test failed but I couldn't figure out how I could fix that. Calling .all method on the Class pass but it fails with associations.
Error Message
0) CustomerDetail #index when logged in should render customer details index page
Failure/Error: #customer_details = current_shop.customer_details.load
NoMethodError:
undefined method `customer_details' for nil:NilClass
# ./app/controllers/customer_details_controller.rb:9:in `index'
ApplicationController
class ApplicationController < ActionController::Base
layout 'embedded_app'
def current_shop
#current_shop ||= Shop.find_by(shopify_domain: cookies[:shopify_domain])
end
end
Here is the Controller
class CustomerDetailsController < ApplicationController
before_action :authenticate_user!
def index
# This failed the test below and complains that NoMethodError: undefined method 'customer_details' for nil:NilClass
#customer_details = current_shop.customer_details.load
#This pass the test below
#customer_details = CustomerDetail.all.load
end
end
Models
class Shop < ActiveRecord::Base
include ShopifyApp::SessionStorage
has_many :customer_details
def api_version
ShopifyApp.configuration.api_version
end
end
class CustomerDetail < ApplicationRecord
belongs_to :shop
end
RSpec
context 'when logged in' do
before do
#shop = create(:shop)
#user = create(:user)
sign_in #user
end
it 'should return a 200 response' do
get customer_details_index_path
expect(response).to have_http_status '200'
end
it 'should render customer details index page' do
get customer_details_index_path
expect(response).to render_template(:index)
end
end
Any help will be appreciated.
current_shop is nil in your controller. It's not enough that you set shop in your spec code. Instance variables from specs are not shared with a controller under test.
Ensure that the shop you're creating in
#shop = create(:shop)
has field shopify_domain set to whatever value has cookies[:shopify_domain] in your test request.
Lets say I have this in application controller:
class ApplicationController < ActionController::Base
def example(&block)
...code here
end
end
and this in another controller:
class OtherController < ApplicationController
def index
example { some_text("irrelevant_text") }
end
private
def some_text var
"#{var}_string"
end
end
I want to test that when calling the index action example method gets called with the irrelevant_text_string argument.
Something like:
describe 'index' do
it 'should call example' do
controller.should_receive(:example).with("irrelevant_text_string")
get :index
end
end
But this isn't working. I keep getting
received :example with unexpected arguments
expected: ("irrelevant_text_string")
got: (no args)
Why does it say no args?
Any help will be appreciated
Because you are passing a block to it, not an argument.
I have an application that detects the subdomain on a request and sets the result to a variable.
e.g.
before_filter :get_trust_from_subdomain
def get_trust_from_subdomain
#selected_trust = "test"
end
How can I test this with Test::Unit / Shoulda? I don't see a way of getting into ApplicationController and seeing what's set...
The assigns method should allow you to query the value of #selected_trust. To assert that its value equals "test" as follows:
assert_equal 'test', assigns('selected_trust')
Given a controller foo_controller.rb
class FooController < ApplicationController
before_filter :get_trust_from_subdomain
def get_trust_from_subdomain
#selected_trust = "test"
end
def index
render :text => 'Hello world'
end
end
one might write a functional test as follows in foo_controller_test.rb:
class FooControllerTest < ActionController::TestCase
def test_index
get :index
assert #response.body.include?('Hello world')
assert_equal 'test', assigns('selected_trust')
end
end
Related to comment: note that the filter can be placed in ApplicationController and then any derived controller will also inherit this filter behaviour:
class ApplicationController < ActionController::Base
before_filter :get_trust_from_subdomain
def get_trust_from_subdomain
#selected_trust = "test"
end
end
class FooController < ApplicationController
# get_trust_from_subdomain filter will run before this action.
def index
render :text => 'Hello world'
end
end
ApplicationController is global, have you considered writing a Rack Middleware instead? Way easier to test.
I've opted for this in another controller in the application:
require 'test_helper'
class HomeControllerTest < ActionController::TestCase
fast_context 'a GET to :index' do
setup do
Factory :trust
get :index
end
should respond_with :success
should 'set the trust correctly' do
assert_equal 'test', assigns(:selected_trust)
end
end
end
They don't seem to be accessible from ActionView::TestCase
That's right, helper methods are not exposed in the view tests - but they can be tested in your functional tests. And since they are defined in the controller, this is the right place to test them. Your helper method is probably defined as private, so you'll have to use Ruby metaprogramming to call the method.
app/controllers/posts_controller.rb:
class PostsController < ApplicationController
private
def format_something
"abc"
end
helper_method :format_something
end
test/functional/posts_controller_test.rb:
require 'test_helper'
class PostsControllerTest < ActionController::TestCase
test "the format_something helper returns 'abc'" do
assert_equal 'abc', #controller.send(:format_something)
end
end
This feels awkward, because you're getting around encapsulation by using send on a private method.
A better approach is to put the helper method in a module in the /controller/concerns directory, and create tests specifically just for this module.
e.g. in app controller/posts_controller.rb
class PostsController < ApplicationController
include Formattable
end
in app/controller/concerns/formattable.rb
module Concerns
module Formattable
extend ActiveSupport::Concern # adds the new hot concerns stuff, optional
def format_something
"abc"
end
end
end
And in the test/functional/concerns/formattable_test.rb
require 'test_helper'
# setup a fake controller to test against
class FormattableTestController
include Concerns::Formattable
end
class FormattableTest < ActiveSupport::TestCase
test "the format_something helper returns 'abc'" do
controller = FormattableTestController.new
assert_equal 'abc', controller.format_something
end
end
You could test #controller.view_context from your functional/controller tests. This method is available in Rails 3, 4, and 5, as far as I can tell.
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
helper_method :current_user
# ...
end
test/controllers/application_controller_test.rb
require 'test_helper'
class ApplicationControllerTest < ActionController::TestCase
test 'current_user helper exists in view context' do
assert_respond_to #controller.view_context, :current_user
end
end
If you didn't want to test one of your controller subclasses, you could also create a test controller to verify that the method in the view_context is the same one from the controller and not from one of your view helpers.
class ApplicationControllerHelperTest < ActionController::TestCase
class TestController < ApplicationController
private
def current_user
User.new
end
end
tests TestController
test 'current_user helper exists in view context' do
assert_respond_to #controller.view_context, :current_user
end
test 'current_user returns value from controller' do
assert_instance_of User, #controller.view_context.current_user
end
end
Or, more likely, you'd want to be able to test the helper in the presence of a request.
class ApplicationControllerHelperTest < ActionController::TestCase
class TestController < ApplicationController
def index
render plain: 'Hello, World!'
end
end
tests TestController
def with_routing
# http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-with_routing
# http://guides.rubyonrails.org/routing.html#connecting-urls-to-code
super do |set|
set.draw do
get 'application_controller_test/test', to: 'application_controller_test/test#index'
end
yield
end
end
test 'current_user helper exists in view context' do
assert_respond_to #controller.view_context, :current_user
end
test 'current_user returns value from controller' do
with_routing do
# set up your session, perhaps
user = User.create! username: 'testuser'
session[:user_id] = user.id
get :index
assert_equal user.id, #controller.view_context.current_user.id
end
end
end
I've struggled with this for a bit, because the accepted answer didn't actually test whether the method was exposed as a helper method.
That said, we can use the #helpers method to get a proxy for testing.
For example:
class FooController < ApplicationController
private
def bar
'bar'
end
helper_method :bar
end
Can be tested with:
require 'test_helper'
class FooControllerTest < ActionController::TestCase
test 'bar is a helper method' do
assert_equal 'bar', #controller.helpers.bar
end
end
Indeed they're not. The view tests are specifically for the views. They don't load the controllers.
You should mock this method and make it return whatever is appropriate depending of your context.