Rails: How do I write tests for a ruby module? - ruby-on-rails

I would like to know how to write unit tests for a module that is mixed into a couple of classes but don't quite know how to go about it:
Do I test the instance methods by writing tests in one of the test files for a class that includes them (doesn't seem right) or can you somehow keep the tests for the included methods in a separate file specific to the module?
The same question applies to the class methods.
Should I have a separate test file for each of the classes in the module like normal rails models do, or do they live in the general module test file, if that exists?

IMHO, you should be doing functional test coverage that will cover all uses of the module, and then test it in isolation in a unit test:
setup do
#object = Object.new
#object.extend(Greeter)
end
should "greet person" do
#object.stubs(:format).returns("Hello {{NAME}}")
assert_equal "Hello World", #object.greet("World")
end
should "greet person in pirate" do
#object.stubs(:format).returns("Avast {{NAME}} lad!")
assert_equal "Avast Jim lad!", #object.greet("Jim")
end
If your unit tests are good, you should be able to just smoke test the functionality in the modules it is mixed into.
Or…
Write a test helper, that asserts the correct behaviour, then use that against each class it's mixed in. Usage would be as follows:
setup do
#object = FooClass.new
end
should_act_as_greeter
If your unit tests are good, this can be a simple smoke test of the expected behavior, checking the right delegates are called etc.

Use inline classes (I am not doing any fancy flexmock or stubba/mocha usage just to show the point)
def test_should_callout_to_foo
m = Class.new do
include ModuleUnderTest
def foo
3
end
end.new
assert_equal 6, m.foo_multiplied_by_two
end
Any mocking/stubbing library out there should give you a cleaner way to do this. Also you can use structs:
instance = Struct.new(:foo).new
class<<instance
include ModuleUnderTest
end
instance.foo = 4
If I have a module that is being used in many places I have a unit test for it which does just that (slide a test object under the module methods and test if the module methods function properly on that object).

What I like to do is create a new host class and mix the module into it, something like this:
describe MyModule do
let(:host_class) { Class.new { include MyModule } }
let(:instance) { host_class.new }
describe '#instance_method' do
it 'does something' do
expect(instance.instance_method).to do_something
end
end
end

I try to keep my tests focused only on the contract for that particular class/module. If I've proven the module's behavior in a test class for that module (usually by including that module in a test class declared in the spec for that module) then I won't duplicate that test for a production class that uses that module. But if there's additional behavior that I want to test for the production class, or integration concerns, I'll write tests for the production class.
For instance I have a module called AttributeValidator that performs lightweight validations kind of similar to ActiveRecord. I write tests for the module's behavior in the module spec:
before(:each) do
#attribute_validator = TestAttributeValidator.new
end
describe "after set callbacks" do
it "should be invoked when an attribute is set" do
def #attribute_validator.after_set_attribute_one; end
#attribute_validator.should_receive(:after_set_attribute_one).once
#attribute_validator.attribute_one = "asdf"
end
end
class TestAttributeValidator
include AttributeValidator
validating_str_accessor [:attribute_one, /\d{2,5}/]
end
Now in a production class that includes the module, I won't re-assert that the callbacks are made, but I may assert that the included class has a certain validation set with a certain regular expression, something particular to that class, but not reproducing the tests I wrote for the module. In the spec for the production class, I want to guarantee that particular validations are set, but not that validations work in general. This is a kind of integration test, but one that doesn't repeat the same assertions I made for the module:
describe "ProductionClass validation" do
it "should return true if the attribute is valid" do
#production_class.attribute = #valid_attribute
#production_class.is_valid?.should be_true
end
it "should return false if the attribute is invalid" do
#production_class.attribute = #invalid_attribute
#production_class.is valid?.should be_false
end
end
There is some duplication here (as most integration tests will have), but the tests prove two different things to me. One set of tests prove the general behavior of the module, the other proves particular implementation concerns of a production class that uses that module. From these tests I know that the module will validate attributes and perform callbacks, and I know that my production class has a specific set of validations for specific criteria unique to the production class.
Hope that helps.

In minitest since each test is explicitly a class you can just include the module to the test and test the methods:
class MyModuleTest < Minitest::Test
include MyModule
def my_module_method_test
# Assert my method works
end
end

I would generally test the module in as much isolation as possible, essentially testing the methods, with just enough code, mocks and stubs to get it working.
I would then probably also have tests for the classes the modules is included in. I may not test every class, but would test enough of the classes to get good coverage and have insight into any issues that arise. These tests don't need to explicitly test the module, but would certainly test it's usage in particular scenarios.
Each set of tests would have its own file.

Related

What's the best way to organize and/or store a mock object I consistently use in RSpec?

I built a client class that sends requests to the Discord API. I mock this client as showed in the sample code below. Please see method #mock_client:
require 'rails_helper'
require 'discord_client'
RSpec.describe some_service_class do
describe '#call' do
let(:client) { mock_client }
it 'does this using discord_client' do
client
described_class.new.call
expect(client).to have_received(:new).once
expect(client).to have_received(:get_guild).once
end
end
private
def mock_client
client = instance_double(DiscordClient)
allow(DiscordClient).to receive(:new).and_return(client)
allow(client).to receive(:get_guild)
allow(client).to receive(:get_user)
client
end
end
However, since I use this client in many services and rake tasks, I don't want to always keep mocking and stubbing it in every spec files I write. Where can I move my method #mock_client so that I can call it in any spec file? Thanks in advance!
In RSpec you can use shared contexts to share your test dependencies (let, let!) or test setup. This is basically a block thats evaluated in the context of the example group its included in:
RSpec.shared_context "Discord mocks" do
let(:client) { instance_double(DiscordClient) }
before do
allow(DiscordClient).to receive(:new).and_return(client)
allow(client).to receive(:get_guild)
allow(client).to receive(:get_user)
end
end
These can either be included manually in indivual specs with include_context or through your spec setup. Shared contexts are typically placed somewhere in /spec/support.
On a side note you can decrease the need of stubbing in the first place by providing factory methods which should be used instead of new.get_guild if you don't need separate initialization and "call" arguments:
class DiscordClient
def self.get_guild(...)
new.get_guild(...)
end
end
Then all you need to do is stub the class methods that your client exposes:
allow(DiscordClient).to receive(:get_guild)
You'll find this pattern used extensively in Service Objects.

Should I create helper methods in test files?

Is it a good practice to create helper method in test file if this method is specific only to tests in this file?
For example, in:
test/integration/post_index_test.rb
among some tests i have defined helper method which is applicable only to tests in this file:
require 'test_helper'
class PostIndexTest < ActionDispatch::IntegrationTest
test 'some test' do
this_file_specific_helper_method
end
def only_this_file_specific_helper_method
# method body
end
end
Can I do this, or it should be avoided. If not, where should I put this method. In generic
test/test_helper.rb
file? Should I create a separate file? Or maybe there is something wrong with my design and I shouldn't have a need for this method at all?
Yes, you can use helper methods within your test files. I don't see why not. If the method performs a routine that you will be using many times within that specific test, using a private helper method would be the way to go.

How to organize minitest/unit tests?

After using RSpec for several projects, I'm giving minitest/unit a go. I'm liking it so far, but I miss using describe/context blocks to group my tests/specs in a logical way.
I know minitest/spec provides this functionality, but I like that minitest/unit feels a bit closer to barebones Ruby.
Are there any gems that provide describe/context support for minitest/unit? Or, should I just live with my long, unorganized test files in minitest/unit?
I know several folks coming from RSpec to minitest struggling with the same question. They love the ability to nest using describe/context blocks and want to continue in minitest. There are several solutions:
Use minitest's spec DSL: While there are minor differences, the spec DSL gives you most (all?) of the good parts of the rspec DSL. The big difference is the lack of context blocks. But you can just as easily use describe in its place and everything works as you'd expect.
Use directories and files: I prefer this option. I dislike scrolling through a 300 line test file, regardless whether its using the spec DSL or the classical xUnit style. I do not find nesting unrelated tests helpful. The same rules for comprehension for code applies to tests. So break it up. Create a directory and place several files within it.
Here is an example of how my test files are organized:
test/
models/
user/
authentication_test.rb
email_test.rb
reservation_test.rb
user_test.rb
username_test.rb
I use this structure whether I'm using the spec DSL or the xUnit style. When using the spec DSL I specify what I'm testing in my describe block like so:
require "minitest_helper"
describe User, :authentications do
before do
# ...
You can also throw multiple classes into one test file:
module PizzaTest
class Isolation < ActiveSupport::TestCase
test "is awesome by default" do
assert Pizza.new.awesome?
end
end
class Integration < ActiveSupport::TestCase
fixtures :all
test "is awesome too" do
pizzas('one-with-everything').awesome?
end
end
end
and even nest test classes:
class PizzaTest < ActiveSupport::TestCase
test "is awesome by default" do
assert Pizza.new.awesome?
end
class Integration < ActiveSupport::TestCase
fixtures :all
test "is awesome too" do
assert pizzas('one-with-everything').awesome?
end
end
end
I prefer this way (only a little bit) but I think it easier to follow:
class ConventionalNameTest < ActiveSupport::TestCase
class ContextTest < ConventionalNameTest
# so much stuff...
end
class AnotherContextTest < ConventionalNameTest
# and some more...
end

How to access a controller constant in an rspec test under Rails 3

Using Rails 3.2 I have a controller in a subdirectory (e.g. /controllers/data_feeds/acme_feed_controller.rb)
This controller has some constants as below
class DataFeeds::AcmeFeedController < ApplicationController
MY_CONSTANT = "hi
def do_something do
...
end
end
In my rspec controller spec (which is in /spec/controllers/data_feeds/acme_feed_controller_spec.rb) I want to access that constant and below are two ways I've tried it (both commented out in the code below)
describe AcmeFeedController do
if "tests something" do
#c = AcmeFeedController.MY_CONSTANT
#c = DataFeeds::AcmeFeedController.MY_CONSTANT
end
end
I'm clearly not understanding something about the scope in which the spec test is run. What do I need to do and equally important why (i.e. what's happening with the scopes).
Thanks for your help.
Constants cannot be referenced with dot syntax, so DataFeeds::AcmeFeedController.MY_CONSTANT would never work in any context. You need to use :: to reference constants: DataFeeds::AcmeFeedController::MY_CONSTANT.
Note that is a ruby issue and has nothing to do with RSpec. When you face an issue like this, I recommend you figure out how to do it with plain ruby (e.g. in IRB) before worrying about how it works in RSpec (usually it will be the same, anyway).
If you want to know how constants work in ruby, I commend you watch this talk that explains them in detail.
Also, you can do this without repeating controller class name spaces.
describe AcmeFeedController do
if "tests something" do
c = controller.class.const_get('MY_CONSTANT')
end
end
This kind of trick may not be approved in application codes, but in tests it may be.

Adding rspec test for library module doesn't seem to pickup Expectations and Matchers

I'm adding more rspec testing to my app and would like to test a ScoringMethods module, which is in /lib/scoring_methods.rb. So I added a /spec/lib directory and added scoring_methods_spec.rb there. I required spec_helper and set up the describe block as so:
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe ScoringMethods do
describe "should have scorePublicContest method" do
methods = ScoringMethods.instance_methods
methods[0].should match(/scorePublicContest/)
end
end
Now methods[0] is a String and there is no problem matching the public method name with the regular expression. And the relative path to "spec_helper" is correct.
The problem is that the entire setup doesn't seem to use the rspec library.
Running the example yields:
./spec/lib/scoring_methods_spec.rb:7: undefined method `match' for Spec::Rails::Example::RailsExampleGroup::Subclass_1::Subclass_1:Class (NoMethodError)
...
The entire Expectation and Matcher support seems to be missing. To test my supposition, I changed a working helper spec by replacing "is_instance_of" to "is_foobar_of". That test simply fails and says "is_foobar_of" is not a method of the targeted object; that it, this entire Spec::Rails::Example... hierarchy isn't present.
I've tried using other matchers as well. I've tried "be_instance_of" and some others. It seems that I'm not including the rspec library properly.
Finally, ScoringMethods is a module, just the same way Helpers are modules. So I thought that it would be possible to test a module (as opposed to classes, such as Controllers and Models).
I'd greatly appreciate your thoughts on what I've done wrong. Perhaps there is a more effective way of testing library modules? Thanks!
You should include your test block in an "it" block. For example:
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe ScoringMethods do
describe "should have scorePublicContest method" do
it "should have a scorePublicContest method" do
methods = ScoringMethods.instance_methods
methods[0].should match(/scorePublicContest/)
end
end
end
You will find that the methods names returned aren't guaranteed to be in the order they exist in the file.
A model we often use when testing Modules is to include the module in either a class created for the test (inside the spec file) or included inside the spec itself.

Resources