Using Rails Test Syntax in Ruby - ruby-on-rails

Like most people, I have learned Rails before Ruby and now I would like to get a better knowledge of Ruby.
I am making some scripts and I would like to test them.
I was surprised by the fact that you can't use Rails' test syntax in plain ruby scripts. The following code does not work:
require "test/unit"
class MyTest < Test::Unit::TestCase
test "one plus one should be equal to two" do
assert_equal 1 + 1, 2
end
end
# Error: wrong number of arguments (1 for 2) (ArgumentError)
You have to use this code instead:
require "test/unit"
class MyTest < Test::Unit::TestCase
def one_plus_one_should_be_equal_to_two
assert_equal 1 + 1, 2
end
end
Which seems less readable to me.
Is it possible to use the "declarative" syntax in plain Ruby scripts?

According to Rails' APIs, the test method is defined inside ActiveSupport::Testing::Declarative module and uses a sort of metaprogramming to add new test methods.
If you don't already have the Rails gem installed, you can just install the activesupport gem:
gem install activesupport
Now you just have to require it and make your class inherit from ActiveSupport::TestCase.
Here is the complete code:
require "test/unit"
require "active_support"
class MyTest < ActiveSupport::TestCase
test "one plus one should be equal to two" do
assert_equal 1 + 1, 2
end
end

Related

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 to stub a method in ActiveSupport::TestCase

In RSpec I could stub method like this:
allow(company).to receive(:foo){300}
How can I stub a method with ActiveSupport::TestCase?
I have a test like this.
class CompanyTest < ActiveSupport::TestCase
test 'foobar' do
company = companies(:base)
#company.stubs(:foo).returns(300)
assert_nil(company.calculate_bar)
end
end
Minitest comes with a stub method out of the box, in case you don't wanna use external tools:
require 'minitest/mock'
class CompanyTest < ActiveSupport::TestCase
test 'foobar' do
company = companies(:base)
Company.stub :foo, 300 do
assert_nil(company.calculate_bar)
end
end
end
Minitest has some limited functionality for mocks, but I'd suggest using the mocha gem for these kinds of stubs.
The syntax for Mocha is exactly what you have on the commented out line:
class CompanyTest < ActiveSupport::TestCase
test 'foobar' do
company = companies(:base)
company.stubs(:foo).returns(300)
assert_nil(company.calculate_bar)
end
end
Enhancing #Farrukh answer:
If you want to verify the arguments passed, like allow(company).to receive(:foo).with(some_args).and_return(300),
You can use assert_called_with.
# requiring may not be needed, depending on ActiveSupport version
require "active_support/testing/method_call_assertions.rb"
include ActiveSupport::Testing::MethodCallAssertions # so we can use `assert_called_with`
assert_called_with(company, :foo, some_args, returns: 300) do
assert_nil(company.calculate_bar)
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.

Testing a Class inside of a Module with RSpec

So, I have a module in my ruby code which looks something like this:
module MathStuff
class Integer
def least_factor
# implementation code
end
end
end
and I have some RSpec tests in which I would like to test that my Integer#least_factor method works as expected. we'll say that the tests are in the same file for simplicity. The tests look something like this:
describe MathStuff do
describe '#least_factor' do
it 'returns the least prime factor' do
expect(50.least_factor).to eq 2
end
end
end
Unfortunately, when I run the tests, I get an error like this:
NoMethodError:
undefined method `least_factor' for 50:Fixnum
Please let me know if you know how to include the MathStuff::Integer class for testing.
Note: just for clarification, I am actually trying to open up the Ruby Integer class here and add methods to it.
Your code should look like:
describe MathStuff::Integer do
describe '#least_factor' do
it 'returns the least prime factor' do
expect(MathStuff::Integer.new.least_factor).to eq 2
end
end
end
But you're calling 50.least_factor and 50 is a Fixnum object, not your MathStuff::Integer and it doesn't have that method defined.
Before the addition of refinements in Ruby 2.1 (and experimental support in 2.0), you couldn't limit the scope of a monkeypatch like this to a particular context (i.e. a module).
But the reason your example doesn't work is that defining an Integer class under the Mathstuff module creates a new class which has nothing to do with the Integer core class. The only way to override the core class is to open the class at the top level (not within a module).
I usually put core extensions in a lib/core_ext subdirectory, named after the class they are patching, in your case lib/core_ext/integer.rb.
Simple but not recomended way:
require "rspec"
class Integer
def plus_one
self + 1
end
end
describe 'MathStuff' do
describe '#plus_one' do
it 'should be' do
expect(50.plus_one).to eq 51
end
end
end
$ rspec test.rb
.
Finished in 0.01562 seconds
1 example, 0 failures

Using Module to group tests in Test::Unit

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_/

Resources