how do I test a private method on controller, using minitest? - ruby-on-rails

Very new to testing
I have a model-less rails app with a controller called "PokerController"
in poker_controller.rb, I have a private method that looks like this:
private
def test(x)
1 + x
end
Then I have a file called 'poker_controller_test.rb' in test/controllers
I'm trying to do something like this:
require 'test_helper'
class PokerControllerTest < ActionController::TestCase
test "check if test == 2" do
test = test(1)
assert test == 2
end
end
As you can see, I'm trying to save the result of the function 'test' being called, to a variable called 'test' then I'm checking to see if that == 2.
Basically, I'm trying to pass in a number (in this case '1') to the test method, and I want the test to add 1 to 1, and expect to get 2.
I'm sure I'm just not setting up the test right, but how do I call a custom method like this and then evaluate what's returned?
Here's my result from the error in the test:
Error:
PokerControllerTest#test_check_if_test_==_2:
ArgumentError: unknown command "\x01"
test/controllers/poker_controller_test.rb:6:in `test'
test/controllers/poker_controller_test.rb:6:in `block in <class:PokerControllerTest>'

You don't.
Testing private methods in general is frowned upon*.
In Rails you mainly test controllers through integration tests. These are tests that send real HTTP requests to your application and then you write assertions about the response or the side effects of sending the request.
"[...] You can test what cookies are set, what HTTP code is returned, how the view looks, or what mutations happened to the DB, but testing the innards of the controller is just not a good idea."
- David Heinemeier Hansson
An example of an integration test is:
require "test_helper"
class PokerControllerTest < ActionDispatch::IntegrationTest
test "can see the welcome page" do
get "/path/to/somewhere"
assert_select "h1", "Welcome to my awesome poker app"
end
end
If you have a method that you want to test in isolation it does not belong in your controller in the first place and arguably it should not be a private method either.
You should test the public API of your objects. The public API of a controller is the methods that respond to HTTP requests via the router.
The use of ActionController::TestCase is discouraged outside of legacy applications.
New Rails applications no longer generate functional style controller
tests and they should only be used for backward compatibility.
Integration style controller tests perform actual requests, whereas
functional style controller tests merely simulate a request. Besides,
integration tests are as fast as functional tests and provide lot of
helpers such as as, parsed_body for effective testing of controller
actions including even API endpoints.
Besides that the issues with your test is that you're calling the test method on the instance of ActionController::TestCase and not your controller. You're then shadowing the method by defining an instance variable with the same name which is rarely a good thing.
Even if you did call the method on the controller Ruby would still raise an error since you're calling a private method from outside the object - thats the whole point of private methods.
While you could violate encapsulation by calling #controller.send(:test) this is just a bad idea on so many levels.

Related

How does assigns know where to look for the instance variable in integration tests?

I've been following a rails tutorial and got to know the assigns method of Rspec.
This is how it's being used in an integration test:
class SomeTest < ActionDispatch::IntegrationTest
test "simplified test" do
get '/some/path'
#some assertions
get '/other/path'
assert_equal 'changed', assigns(:some_variable)
end
end
Assigns seem to access the controller/view of the last call.
Is that so? how does assigns get to the instance variable some_variable?
It seems to imply there is some state being preserved in the integration test that is accessed from assigns.
It turned out that Rails treat Integration tests pretty much like functional ones, so the instance variables described in the documentation for the latter works in the former. In integration tests these variables tend to be overwritten, so you'll access the state prior to your call.
That means that in an integration test you may access:
#controller: the last controller being invoked
#request: the last request sent
#response: the last response object returned
assings just wraps the access to #controller.view_assigns where all the instance variables that will be available for the view are held.

'assigns' method not found in rspec capybara

I have the following code in my controller:
private
def remaining_words
#remaining_words = Vocab.all.where.not(id: session[:vocab_already_asked])
#questions_remaining = #remaining_words.length - 4
#quiz_words = #remaining_words.shuffle.take(4)
And here is my test:
feature 'Quiz functionality' do
scenario "gets 100% questions right in quiz" do
visit(root_path)
visit(start_quiz_path)
assigns(:questions_remaining).length.to_i.times do
orig_value = find('#orig', visible: false).value
choose(option: orig_value)
click_on('Submit')
expect(page).to have_content('You got it right!')
expect(page).not_to have_content('Sorry, wrong answer!')
end
expect(page).to have_content("Your score is 27/27")
save_and_open_page
end
end
I get the error message when I run the test:
NoMethodError: undefined method `assigns' for #<RSpec::ExampleGroups::QuizFunctionality:0x007f8f2de3f2b0>
# ./spec/features/quizzes_spec.rb:9:in `block (2 levels) in <top (required)>'
I've also tried using controller.instance_variable_get(:remaining_words) and get this error message
NameError:
undefined local variable or method `controller' for #<RSpec::ExampleGroups::QuizFunctionality:0x007fc4b99251a0>
Am I missing something in setting up the test? Should I be using describe instead of feature to enable the assign method?
assigns was solely available in controller tests - it was depreciated in Rails 5.
Testing what instance variables are set by your controller is a bad
idea. That's grossly overstepping the boundaries of what the test
should know about. You can test what cookies are set, what HTTP code
is returned, how the view looks, or what mutations happened to the DB,
but testing the innards of the controller is just not a good idea.
- David Heinemeier Hansson
In RSpec controller specs wrap the deprecated ActionController::TestCase.
A controller spec is identified by having the type: :controller metadata.
RSpec.describe ThingsController, type: :controller do
# ...
describe "GET #index" do
end
end
If you have set config.infer_spec_type_from_file_location! in config.infer_spec_type_from_file_location! RSpec will infer that any spec in spec/controllers has type: :controller.
You should avoid controller specs for new applications in favor of request and feature specs. One of the main problems with controller specs besides the violation of encapsulation is that the entire request phase is stubbed, the request does not actually go through rack or the routes which can mask routing errors and means that Rack middleware like Warden (used by Devise) or sessions must be stubbed.
If you have a legacy application you can reintroduce assigns with a gem. If you are just learning RSpec you should select more up to date tutorials.
Feature specs are high-level tests meant to exercise slices of
functionality through an application. They should drive the
application only via its external interface, usually web pages.
https://relishapp.com/rspec/rspec-rails/v/3-7/docs/feature-specs
Use feature specs for high level tests centered on the user story. Use RSpec.feature "New Cool Feature" to write a feature spec.
Request specs provide a thin wrapper around Rails' integration tests,
and aredesigned to drive behavior through the full stack, including
routing (provided by Rails) and without stubbing (that's up to you).
https://relishapp.com/rspec/rspec-rails/v/3-7/docs/request-specs/request-spec
Use RSpec.describe "Some resource", type: :request to write a feature spec.
Request specs are invaluable for testing API' or when you just need fast tests that ensure that the correct mutations happened to the DB or that the correct http responses are sent.
See:
https://blog.bigbinary.com/2016/04/19/changes-to-test-controllers-in-rails-5.html
https://github.com/rails/rails/issues/18950
You're writing feature specs/integration tests which don't have access to the controller/controller instance variables. They are meant to be more of a black box test executed from the users perspective. When setting up the data for the test you should know how many questions need to be asked and then either hardcode that in your test, or, better yet, detect based on the page contents whether there are more questions to answer (just like a user would have to).

Testing unit with skip_before_action does not unset after using TDD

I am using Rails 5.2 and doing some testing. I am trying my unit testing with ActionController::TestCase and found that #controller.class.skip_before_action :verify_user does not reset the controller automatically before going to the next test unit method.
Now because rails tests are randomized at every run, certain unit fails sometimes and not other times. I figured it was expecting HTTP 401 but I got 200. Cause the controller ignored to before_action :verify_user.
I could set #controller.class.before_action :verify_user at end of every unit (and it works!) but should it not be the rep of testing sys to reset context before every run?
Giving a snip of my code:
class ApiSiteMetricsTest < ActionController::TestCase
tests Api::SiteMetricsController
def test_1_index
#controller.class.skip_before_action :verify_user,raise: false
get "index", params:{
"format"=>"json",
"site_metric_value"=>{
"site_metric_id"=>2403,
"date_acquired"=>"2018-03-14T01:44:00+05:30",
"site_id"=>3840,
"lab_device_details"=>"",
"comment"=>"",
"sender_affiliation"=>"",
"float_value"=>""
}
}
assert_response :success
File.open("#{Rails.root}/del.html", "wb") { |f| f.write(#response.body) }
#Do I have to do this o every test?
##controller.class.before_action :verify_user
end
...
You are absolutely right that each test should reset the state of the system after running. Tests should should be entirely independent - which is precisely why they are run in a random order (by default).
For most things - such as database transactions - the test framework can handle this for you. But there are infinitely other ways that you could alter the environment; the test framework cannot always cover your back.
For example, what if your test changes an ENV variable? Or calls Timecop.freeze? Or adds a database record via a second database connection? Or sets a global variable? ...
Sometimes, you need to reset the state manually!
In this case, I would do:
class ApiSiteMetricsTest < ActionController::TestCase
tests Api::SiteMetricsController
def test_1_index
#controller.class.skip_before_action :verify_user,raise: false
# ...
ensure
#controller.class.before_action :verify_user
end
end
The ensure is there so that even if this test fails, the state should be reset regardless - therefore not affecting whether or not other tests fail.
In some cases, you may find it convenient to use MiniTest's setup and teardown methods to provide this functionality. (Equivalent to the before and after hooks in rspec).

Helper functions in Rails unit tests

I'm using this sort of code in my code in my Unit tests.
test "should be awesome" do
assert true
end
I'm using FactoryGirl instead of fixtures.
I find that I'm repeating myself a lot and having helper functions would be quite useful. What's the best way to create and call a helper function in the unit test? Is there a before_filter available like it is in the controllers (I tried putting it in, but it's just an undefined method). Any help is appreciated!
You can add utility functions to your unit tests without a problem. As long as you don't call them name them like "test_something" they won't get run as unit tests. You can then call them from your actual unit test methods (the format you use boils down to having a method in the class anyway).
So:
test "should be awesome" do
assert_general_awesomeness
assert true
end
private
def assert_general_awesomeness
assert true
end
Utility methods that are going to be used all over the place can go in test_helper and they will be available to all your tests. You could alternatively have a module that you mix in to your tests to provide common utility methods.
If you are doing common calls to set up before a unit test you can put in in a setup method which will be called before each test in the class.

how to omit something from an rspec controller test?

In my RSpec controller test, I'm trying to test a particular action that makes a call to a subfunction. But I don't want the test to include the call to that subfunction. (it'll raise an error that simply cannot be fixed in my dev environment)
What's the best way to omit this subfunction call from this controller action while I'm running a test over it?
Thanks.
You can stub that subfunction call in the before block of your rspec test for that function like so
describe "test for the main_action"
before(:each) do
controller.stub!(:sub_action).returns(true)
end
end
Then none of your test examples would actually call this sub_action.
Just for semantics, Rspec always runs in test environment not in development but I gather what you mean.

Resources