SCENARIO
I have extracted a concern called Taggable
. It's a module that allows any model to support tagging. I have included this concern/module into models like User
, Location
, Places
, Projects
.
I want to write tests for this module, but don't know where to start.
QUESTION
1. Can I do isolation testing on the Taggable
concern?
In the example below the test fails because the test is looking for a dummy_class table
. I'm assuming it's doing this because of the has_many
code in Taggable
so as a result it expects 'DummyClass'
to be an ActiveRecord object.
# /app/models/concerns/taggable.rb
module Taggable
extend ActiveSupport::Concern
included do
has_many :taggings, :as => :taggable, :dependent=> :destroy
has_many :tags, :through => :taggings
end
def tag(name)
name.strip!
tag = Tag.find_or_create_by_name(name)
self.taggings.find_or_create_by_tag_id(tag.id)
end
end
# /test/models/concerns/taggable_test.rb
require 'test_helpers'
class DummyClass
end
describe Taggable do
before do
@dummy = DummyClass.new
@dummy.extend(Taggable)
end
it "gets all tags" do
@dummy.tag("dummy tag")
@dummy.tags.must_be_instance_of Array
end
end
Part of me thinks if I just test a model that has this module included inside of it like User
that's enough of a test. But I keep reading that you should test modules in isolation.
Looking for some guidance / strategy on what the right approach is.
Setting Up Minitest. To run a Minitest test, the only setup you really need is to require the autorun file at the beginning of a test file: require 'minitest/autorun' . This is good if you'd like to keep the code small. A better way to get started with Minitest is to have Bundler create a template project for you.
It is a bit of Rails carbohydrates sprinkled upon a Ruby module. What ActiveSupport::Concern does for you is it allows you to put code that you want evaluated inside the included block. For example, you want to extract the trashing logic out of your model.
A Rails Concern is a module that extends the ActiveSupport::Concern module. Concerns allow us to include modules with methods (both instance and class) and constants into a class so that the including class can use them. A concern provides two blocks: included.
Most methods can be tested by saying, “When I pass in argument X, I expect return value Y.” This one isn't so straightforward though. This is more like “When the user sees output X and then enters value V, expect subsequent output O.” Instead of accepting arguments, this method gets its value from user input.
I would suggest having DummyClass
be a generic ActiveRecord::Base
child with very little custom code besides just include Taggable
, so that you would be isolating your concern module as much as possible but still being an AR class. Avoiding the use of one of your "real" classes like User
still isolates you from any other code in those classes, which seems valuable.
So something like this:
class DummyClass < ActiveRecord::Base; end
describe Taggable do
before do
@dummy_class = DummyClass.new
end
...
end
Since your DummyClass
may need to actually interact with the DB to test things like associations, you may need to create temporary tables in the DB during testing. The temping Ruby gem may be able to help with that, since its designed to create temporary ActiveRecord models and their underlying database tables.
Temping allows you to create arbitrary ActiveRecord models backed by a temporary SQL table for use in tests. You may need to do something like this if you're testing a module that is meant to be mixed into ActiveReord models without relaying on a concrete class.
I went with using ActiveRecord Tableless rather than Temping gem, which seems to be a little out of date at the moment.
I set up my test up exactly the same as Stuart M has in his answer but included the has_no_table
helper method and columns required in my DummyClass.
class DummyClass < ActiveRecord::Base
# Use ActiveRecord tableless
has_no_table
# Add table columns
column :name, :string
# Add normal ActiveRecord validations etc
validates :name, :presence => true
end
This worked for what I needed to test, which was a module that extended ActiveRecord::Base
with a few additional methods, but I haven't tried it with any has_many
associations so it still might not help with what you wanted to test.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With