Using Module to group tests in Test::Unit - ruby-on-rails

When I have many tests in one test class, I use Module to group the tests.
Since I'm very new to rails and my question is:
Is it correct way to group tests or I'm doing very stupid things without knowing other side-effects?
Here is the code:
require 'test_helper'
module AttributeValidationTest
extend ActiveSupport::Testing::Declarative
test "should not ...." do
# .....
end
# other tests here....
end
module AnotherGroupTest
extend ActiveSupport::Testing::Declarative
# tests.....
end
# may be another modules..
class MyModelTest < ActiveSupport::TestCase
include AttributeValidationTest
include AnotherGroupTest
end
Thanks.

The question is: what do you gain by grouping the tests in modules?
I, in my humble tradition, just group similar tests by placing them not far from each other, and by giving them the same name prefix, like:
def test_user_name_handles_strange_chars
def test_user_name_handles_empty_string
def test_user_name_...
(you can without problems use the new syntax, as test "name should handle strange....")
This helps me to test only parts of the functionality (as my full test suite takes about an hour):
cd test && ruby unit/user_test.rb -n /test_user_name_/

Related

Unit testing code that references Rails models without loading the models

I am trying to unit test a Plain Old Ruby Object that has a method which calls a class method on a Rails model. The Rails app is quite large (10s of seconds to load) so I'd prefer to avoid loading all of Rails to do my unit test which should run in under 1s.
Example:
class Foo
def bar
SomeRailsModel.quxo(3)
end
end
RSpec.describe Foo do
let(:instance) { Foo.new }
it 'calls quxo on SomeRailsModel' do
expect(SomeRailsModel).to receive(:quxo)
instance.bar
end
end
The problem here is that I need to require 'rails_helper' to load up Rails in order for app/models/some_rails_model to be available. This leads to slow unit tests due to Rails dependency.
I've tried defining the constant locally and then using regular spec_helper which kind of works.
Example:
RSpec.describe Foo do
let(:instance) { Foo.new }
SomeRailsModel = Object.new unless Kernel.const_defined?(:SomeRailsModel)
it 'calls quxo on SomeRailsModel' do
expect(SomeRailsModel).to receive(:quxo)
instance.bar
end
end
This code lets me avoid loading all of Rails and executes very fast. Unfortunately, by default (and I like this) RSpec treats the constant as a partial double and complains that my SomeRailsModel constant doesn't respond to the quxo message. Verifying doubles are nice and I'd like to keep that safety harness. I can individually disable the verification by wrapping it in a special block defined by RSpec.
Finally, the question. What is the recommended way to have fast unit tests on POROs that use Rails models without requiring all of Rails while also keeping verifying doubles functionality enabled? Is there a way to create a "slim" rails_helper that can just load app/models and the minimal subset of ActiveRecord to make the verification work?
After noodling a few ideas with colleagues, here is the concensus solution:
class Foo
def bar
SomeRailsModel.quxo(3)
end
end
require 'spec_helper' # all we need!
RSpec.describe Foo do
let(:instance) { Foo.new }
let(:stubbed_model) do
unless Kernel.const_defined?("::SomeRailsModel")
Class.new { def self.quxo(*); end }
else
SomeRailsModel
end
end
before { stub_const("SomeRailsModel", stubbed_model) }
it 'calls quxo on SomeRailsModel' do
expect(stubbed_model).to receive(:quxo)
instance.bar
end
end
When run locally, we'll check to see if the model class has already been defined. If it has, use it since we've already paid the price to load that file. If it isn't, then create an anonymous class that implements the interface under test. Use stub_const to stub in either the anonymous class or the real deal.
For local tests, this will be very fast. For tests run on a CI server, we'll detect that the model was already loaded and preferentially use it. We get automatic double method verification too in all cases.
If the real Rails model interface changes but the anonymous class falls behind, a CI run will catch it (or an integration test will catch it).
UPDATE:
We will probably DRY this up a bit with a helper method in spec_helper.rb. Such as:
def model_const_stub(name, &blk)
klass = unless Kernel.const_defined?('::' + name.to_s)
Class.new(&blk)
else
Kernel.const_get(name.to_s)
end
stub_const(name.to_s, klass)
klass
end
# DRYer!
let(:model) do
model_const_stub('SomeRailsModel') do
def self.quxo(*); end
end
end
Probably not the final version but this gives a flavor of our direction.

Need help understanding Testing in Ruby/Rails using Minitest and Mocha

I was wondering if someone could give a broad explanation (high level overview) of testing using Minitest and Mocha in Rails, and perhaps when to use which types of testing utilities.
I seem to be getting caught up not knowing when to use mock vs stub, expects vs stubs, and the order of operations when mocking and stubbing and knowing how to "break down" the functionality of code.
For an example (and yes, I understand I'm testing side effects, but in this case, I don't know what else to test):
Class to be tested:
# frozen_string_literal: true
class UploadJob < ApplicationJob
queue_as :upload
def perform(batch_id)
Logger.new.time('upload.time') do
Uploader.new(batch_id).upload_data
end
end
end
Initial test that didn't work:
# frozen_string_literal: true
require 'test_helper'
class TestUploadJob < ActiveSupport::TestCase
test 'UploadJob > perform > it should start uploading' do
Logger.stubs(:new).then.stubs(:time).with('upload.time').yields
Uploader.expects(:new).with(1).then.expects(:upload_data)
UploadJob.perform_now(1)
end
End result test that works:
# frozen_string_literal: true
require 'test_helper'
class TestUploadJob < ActiveSupport::TestCase
test 'UploadJob > perform > it should start uploading' do
logger_stub = stub
logger_stub.stubs(:time).with('upload.time').yields
Logger.stubs(:new).returns(logger_stub)
uploader_mock = mock
uploader_mock.expects(:upload_data)
Uploader.expects(:new).with(1).returns(uploader_mock)
UploadJob.perform_now(1)
end
To me, it felt more declarative in the first test to say the exact order things would be called in. In the second test, that works, first I'm stubbing or mocking a method call that comes after the call to <CLASS>.new.
What's a better way of thinking about this/approaching this problem of testing? Are there any good resources specific to testing with Minitest and Mocha, NOT Rspec, that would be suggested reads?
Thanks!

How should I specify files to read from in my Minitest tests for a Rails app?

In my MiniTest-based tests in Rails, I want to read files that represent dummy data - for example, responses used by Webmock, or input from users.
What's the best way to specify the location of these files?
For now, I am using my own helper function that does a File.join to reach the test/fixtures/files folder, and look for a file there.
Is there a more "conventional" way of doing this?
I followed your lead and am also putting my test files in /test/fixtures/files/*.*
# from test_helper.rb
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
self.use_instantiated_fixtures = true
# Add more helper methods to be used by all tests here...
def read_file name
filepath = "#{Rails.root}/test/fixtures/files/#{name}"
return File.read(filepath)
end
end
Then I call it like this:
# from html_import_test.rb
class HtmlImportTest < ActionController::TestCase
test 'read html' do
html = read_file 'test.html'
end
end

Include module in all MiniTest tests like in RSpec

In RSpec I could create helper modules in /spec/support/...
module MyHelpers
def help1
puts "hi"
end
end
and include it in every spec like this:
RSpec.configure do |config|
config.include(MyHelpers)
end
and use it in my tests like this:
describe User do
it "does something" do
help1
end
end
How can I include a module into all MiniTest tests without repeating myself in every test?
From the Minitest README:
=== How to share code across test classes?
Use a module. That's exactly what they're for:
module UsefulStuff
def useful_method
# ...
end
end
describe Blah do
include UsefulStuff
def test_whatever
# useful_method available here
end
end
Just define the module in a file and use require to pull it in. For example, if 'UsefulStuff' is defined in test/support/useful_stuff.rb, you might have require 'support/useful_stuff' in either your individual test file.
UPDATE:
To clarify, in your existing test/test_helper.rb file or in a new test/test_helper.rb file you create, include the following:
Dir[Rails.root.join("test/support/**/*.rb")].each { |f| require f }
which will require all files in the test/support subdirectory.
Then, in each of your individual test files just add
require 'test_helper'
This is exactly analogous to RSpec, where you have a require 'spec_helper' line at the top of each spec file.
minitest does not provide a way to include or extend a module into every test class in the same way RSpec does.
Your best bet is going to be to re-open the test case class (differs, depending on the minitest version you're using) and include whatever modules you want there. You probably want to do this in either your test_helper or in a dedicated file that lets everyone else know you're monkey-patching minitest. Here are some examples:
For minitest ~> 4 (what you get with the Ruby Standard Library)
module MiniTest
class Unit
class TestCase
include MyHelpers
end
end
end
For minitest 5+
module Minitest
class Test
include MyHelperz
end
end
You can then use the included methods in your test:
class MyTest < Minitest::Test # or MiniTest::Unit::TestCase
def test_something
help1
# ...snip...
end
end
Hope this answers your question!
One thing I will do is create my own Test class inheriting from Minitest::Test. This allows me to do any sort of configuration on my base test class and keeping it isolated to my own project1.
# test_helper.rb
include 'helpers/my_useful_module'
module MyGem
class Test < Minitest::Test
include MyUsefulModule
end
end
# my_test.rb
include 'test_helper'
module MyGem
MyTest < Test
end
end
1 This is most likely unneeded, but I like keeping all my gem code isolated.

how to write and inherit from an abstract subclass of ActionController::TestCase

I have a bunch of Rails 3.1 controllers which all have very similar testing requirements. I have extracted out the common code (all Test::Unit style), e.g. the following three tests are completely reusable across all of them:
def create
new_record = { field_to_update => new_value }
create_params = { :commit => "Create", :record => new_record }
post :create, create_params
end
test "should_not_create_without_login" do
assert_no_difference(count_code) do create; end
assert_need_to_log_in
end
test "should_not_create_without_admin_login" do
login_as_non_admin
assert_no_difference(count_code) do create; end
assert_needs_admin_login
end
test "should_create" do
login_as_admin
assert_difference(count_code) do create; end
assert_redirected_to list_path
end
and I intended that it could go in an abstract class which inherits from ActionController::TestCase. Then each functional test would only need to override the abstract methods, ending up pleasingly small and clean, e.g.
class Admin::AvailabilitiesControllerTest < Admin::StandardControllerTest
tests Admin::AvailabilitiesController
def model ; Availability end
def id_to_change ; availabilities(:maybe).id end
def field_to_update; :value end
def new_value ; 'maybe2' end
def list_path ; admin_availabilities_path end
end
However, when I try this, it appears that the framework tries to run the test methods directly from the abstract class, rather than from the inherited class:
E
===================================================================================================
Error:
test_should_not_create_without_login(Admin::ControllerTestBase):
NoMethodError: undefined method `model' for test_should_not_create_without_login(Admin::ControllerTestBase):Admin::ControllerTestBase
test/lib/admin_controller_test_base.rb:7:in `count_code'
test/lib/admin_controller_test_base.rb:68:in `block in <class:ControllerTestBase>'
===================================================================================================
I've heard that other testing frameworks and gems can provide mechanisms for meta-programming of tests, so maybe I'm going about this in entirely the wrong way. But I've tried several things and looked at RSpec, coulda, shoulda, context, contest ... and I still can't see a way to achieve what I'm after. Any ideas? Thanks!
I finally figured this out - once I realised that this is a general Ruby Test::Unit issue rather a Rails testing issue, a quick google instantly revealed How do I inherit abstract unit tests in Ruby? which already had a good answer. Then the only missing piece was being able to use the syntactic sugar:
test "something should behave in a certain way" do
...
end
rather than
def test_something_should_behave_in_a_certain_way" do
...
end
I found the answer to this within the ActiveSupport codebase itself, under lib/active_support/test_case.rb:
extend ActiveSupport::Testing::Declarative
This module defines test as a class method (which is why extend is required rather than include).
So the complete solution looks like this:
# tests/functional/admin/availabilities_controller_test.rb
class Admin::AvailabilitiesControllerTest < ActionController::TestCase
tests Admin::AvailabilitiesController
include Admin::ControllerTests
# non-reusable tests and helper methods specific to this
# controller test go here
end
# lib/admin/controller_tests.rb
module Admin::ControllerTests
extend ActiveSupport::Testing::Declarative
test "this test can be reused by anything which includes this module" do
...
end
end
The downside of this module-based approach is that you can't override the included tests. I guess that's just a fundamental limitation of Test::Unit - maybe the best answer is to move to RSpec, but I don't know it well enough yet to be sure.

Resources