how to test collections of ActiveRecords in rspec? - ruby-on-rails

I like RSpec, and I really like its =~ array operator matcher to verify that an array contains a specific set of elements, regardless of ordering.
But not surprisingly, it tests pointer equality, not content equality, so the following won't work:
class Flea < ActiveRecord::Base ; end
class Dog < ActiveRecord::Base
has_many :fleas
end
#d = Dog.create
#d.fleas << (#f1 = Flea.create)
#d.fleas << (#f2 = Flea.create)
#d.fleas.should =~ [#f1, #f2]
So I find myself frequently writing this in my RSpec tests:
#d.fleas.map {|f| f.id}.should =~ [#f1.id, #f2.id]
... which reeks of bad code smell. Does RSpec provide a better way to verify a collection of ActiveRecord objects, regardless of the order returned? Or at least is there a prettier way to code such a test?

ActiveRecord::Relations don't work like Arrays (as you found out). See Issue #398 on the GitHub RSpec-Rails Issue board.
The answer is to add this line of code to your spec_helper.rb:
RSpec::Matchers::OperatorMatcher.register(ActiveRecord::Relation, '=~', RSpec::Matchers::MatchArray)

Related

How do you specify the exact order of Minitest tests?

Minitest lets you run tests in order by overriding test_order to alpha. (You can also use the i_suck_and_my_tests_are_order_dependent! method.)
After doing this, how do you control the order the tests are run across multiple files?
Is there a way to run some tests from one file and then switch to another file?
Looking at the source code, it seems one should be able to control how methods are sorted. But how do you specify that order?
i_suck_and_my_tests_are_order_dependent! (or def self.test_order; :alpha;end define an alphabetic order.
Beside the alphabetic order there is only a random order defined (at least in 'minitest', '>5.5.1', '<=5.6.1'.
But you can patch MiniTest::Test.runnable_methods to get another order.
gem 'minitest'
require 'minitest/autorun'
class MiniTest::Test
#Add a test order :defined
def self.runnable_methods
methods = methods_matching(/^test_/)
case self.test_order
when :random, :parallel then
max = methods.size
methods.sort.sort_by { rand max }
when :defined then # <-new
methods
when :alpha, :sorted then
methods.sort
else
raise "Unknown test_order: #{self.test_order.inspect}"
end
end
end
class TestOrder < MiniTest::Test
def self.test_order; :defined; end
#Alphabetic order
#~ def self.test_order; :alpha;end
#~ i_suck_and_my_tests_are_order_dependent!
def test_4; p __method__; end
def test_3; p __method__; end
def test_2; p __method__; end
def test_1; p __method__; end
end
But the test order is only defined per Test-subclass, not global for all tests. So this does not give you access to the order of test methods in multiple test classes.
I estimate you have different test-classes in your files, so this would correspond to your problem. (Not the files is the criteria, but the Test-class.)
If you define only one Test class in your test files then you have the possibility to define your own order.
You can add i_suck_and_my_tests_are_order_dependent! to your test class.
Example:
class TestClass < Minitest::Unit::TestCase
i_suck_and_my_tests_are_order_dependent!
def test_true
assert true
end
end
According to ruby-doc,
Call this at the top of your tests when you absolutely positively need to have ordered tests. In doing so, you’re admitting that you suck and your tests are weak.
You can also simply add the following to your test class:
def self.test_order
:alpha
end
The i_suck_and_my_tests_are_order_dependent! method uses that.
Can also change globally for all test suits:
# in your test_helper.rb
require "minitest" # or "minitest/autorun"
class Minitest::Test
def self.test_order
:alpha
end
end
Little addition ( or may be override %) ) to #knut answer. You don't need to patch Minitest, instead just override self.runnable_methods in your TestClass, and place your tests there in any suitable order.
def self.runnable_methods
super | ['run_last']
end
It's better then #knuts answer since original answer is dependent internally on Module method instance_methods(include_super=true), and it will return them in less predictable and partially alphabetically sorted way:
module A
def method3() end
end
class B
include A
def method2() end
end
class C < B
def method4() end
def method1() end
end
C.instance_methods(true) # -> [:method1, :method4, :method2, :method3 ...
so I assume in previously given solution your tests will need alpha sorted names to keep their order. So looks like it's just another way around to :alpha or :sorted way

Rails: testing a class method with FactoryGirl when fixtures are present

I'd like to test a class method for the following model:
class Subscription < ActiveRecord::Base
# ...
self.active_within_timeframe(t1, t2)
# method code
end
end
Here are the factories:
FactoryGirl.define do
factory :subscription do
user_id 1
factory :s1 do
beginning 3.days.ago
ending 2.days.ago
end
factory :s2 do
beginning 1.day.ago
ending nil
end
end
end
In my application I also have fixtures for subscriptions. How can I run tests only against records created by FactoryGirl?
Is there a way to fill out the missing part in the test below?
class UserTest < ActiveSupport::TestCase
test "should find records for the given time frame" do
s1 = FactoryGirl.create(:s1)
s2 = FactoryGirl.create(:s2)
# Create an object (of e.g. ActiveRecord::Relation class) containing
# factories s1 and s2, and nothing from fixtures
# subscriptions = ...
records = subscriptions.active_within_timeframe(2.5.days.ago, 0.5.days.ago)
assert records.order('id desc') == [s1, s2].order('id desc')
end
end
I'd say use fixtures or don't. Don't mix it. You could just add your s1 and s2 to the fixtures. Otherwise just, don't load fixtures and use factories throughout your tests. I use fixtures only to populate the DB in my development env and factories in testing. See this article on the topic. Also for performance reasons, you should consider to use non-persisted (or even stubbed) objects where possbile, which is another advantage of factories over fixtures in testing.
Aside from the testing matter, I think you should use a scope instead of a class method

How to disable belongs_to :touch option in Rspec tests for Rails models?

Having a large model stack and using doll caching techniques extensively, one ends up with lots of parent models been "touched" after a model update.
While testing, this seems to be a time waster unless you try to test that feature specifically.
Is there a way to prevent models to touch their belongs_to associations for the test environment or at a test level?
UPDATE 1:
My first attempt to the case would be to
# /config/initializers/extensions.rb
#
class ActiveRecord::Base
def self.without_touch_for_association(association_name, &block)
association_name = association_name.to_sym
association = self.reflect_on_all_associations(:belongs_to).select { |reflection| reflection.name == association_name }.first
options = association.options
association.instance_variable_set :#options, options.except(:touch)
yield
association.instance_variable_set :#options, options
end
end
Post.without_touch_for_association(:user) do
Post.last.save
end
Of course, no success and saving Post.last still touches it's User.
UPDATING RATIONALE:
I understand and agree that this approach may be a source of bugs and it's not a good practice at all. The thing is that I have a huge suite with lots of both integration and unit tests. Doll caching also gets deep in the model tree. Every time I look at the logs, I see a significant % of touch-related queries. I know the best way would be optimizing the unit tests to add more mocking and stubbing and less persistence. Solving the issue within integration tests is more difficult.
In any case, I'm asking this question for the sake of learning and research. I am interested in exploring the potential speed improvements of this technique.
SOLUTION: see my own answer below for the working code.
Assuming you're on Rails 4.1.4 or newer:
User.no_touching do
Post.last.save
end
or even
ActiveRecord::Base.no_touching do
Post.last.save
end
See ActiveRecord::NoTouching.
I disagree with the notion of altering the code for test purposes. Testing from my point of view should be an independent procedure.
As I see it you should provide a way to your test suite to alter the behavior of a model only for certain cases.
The following code
class Book < ActiveRecord::Base
belongs_to :author, touch: true
end
class Author < ActiveRecord::Base
has_many :books
end
which is your case will define an instance method
belongs_to_touch_after_save_or_destroy_for_author
behind the scene for the Book class.
( thanks to AR http://apidock.com/rails/ActiveRecord/Associations/Builder/BelongsTo/add_touch_callbacks )
So in your test code you could override that method to do something different or nothing at all!
In my case I use Rspec with FactoryGirl, so what I did was to create a special factory trait for the Book class which redefines belongs_to_touch_after_save_or_destroy_for_author for that object
FactoryGirl.define do
factory :book do
...
...
end
trait :no_touch do
before(:create) do |book_no_touch|
def book_no_touch.belongs_to_touch_after_save_or_destroy_for_author
true
end
end
end
end
That way, when you need to test something where touching the related objects is irrelevant, you can create a book object with that factory
book = FactoryGirl.create(:book, :no_touch)
For Rails >= 4.2
Thanks to #Dorian, in Rails 4.2 the way to go is using ActiveRecord::NoTouching.
For Rails < 4.2
My working code in rspec support file:
# /spec/support/active_record_extensions.rb
class ActiveRecord::Base
def self.without_touch_for_association(association, &block)
method_name = :"belongs_to_touch_after_save_or_destroy_for_#{association}"
return unless self.instance_methods.include?(method_name)
method = self.send(:instance_method, method_name)
self.send(:define_method, method_name) { true }
yield
self.send(:define_method, method_name, method)
nil
end
def self.disable_touch_associations!
associations = self.reflect_on_all_associations(:belongs_to)
associations.each do |association|
self.without_touch_for_association association.name do
return
end
end
nil
end
end
Add this to your ./spec/spec_helper.rb to disable all touch calls for any model defined, for the whole test suite:
RSpec.configure do |config|
if ENV['SILENCE_TOUCHES']
config.before :suite do
ActiveRecord::Base.descendants.each {|model| model.disable_touch_associations! }
end
end
end
Temporarely disabling a touch for a model and association in a particular test.
Post.without_touch_for_association(:user) do
Post.last.save
end
Thanks to #xlembouras below for pointing me to the right direction!
I'm playing with this feature on our tests and I'm noticing a 25% reduction in test suite speed, for a 30min test suite. I may post more accurate results after more thorough research.
I'm not sure if this is going to work but you could try the following:
belongs_to :foo, touch: APP_CONFIG['doll_touch']
where APP_CONFIG is an application parameter that is set following this guide.
So, in your production/development part of the configuration, you set doll_touch to true and in your test to false.

How to test ActiveRecord witout Rails?

I have a project in which I use ActiveRecord to store information in a sqlite db file. I'm not using Rails and AR seems to do the job perfectly. My question is how exactly to test my classes witout hitting the db? I found some gems that would to the trick (FactoryGirl, UnitRecord), but they are meant to work with Rails.
class News < ActiveRecord::Base
belongs_to :feed
def delete_old_news_if_necessary
# time_limit = Settings::time_limit
return if time_limit.zero?
News.destroy_all("date < #{time_limit}")
end
def delete_news_for_feed(feed_id)
News.destroy_all(:id => feed_id)
end
def news_for_feed(feed_id)
News.find(feed_id)
end
end
I read that i can do a column stub:
Column = ActiveRecord::ConnectionAdapters::Column
News.stubs(:columns).returns([Column.new(),...])
Is this the right way to do these tests? Also, when is it better to have a separate db just for testing and to create it, run the tests, and the delete it?
If you want to avoid hitting the db in tests I can recommend the mocha gem. It does stubs as well as it lets you define expectations.
Edit: Regarding your question on when it is better to use a test db: I would say, whenever there is no reason against it. :)
Edit: For example, you can mock News.find like this in a test:
def news_for_feed_test
# define your expectations:
news = News.new
News.expects(:find).with(1).returns(news)
# call the method to be tested:
News.new.news_for_feed(1)
end
At the same time this makes sure, find gets called exactly once. There are a lot more things Mocha can do for you. Take a look at the documentation. Btw., it looks like these methods of yours should be class methods, no?

Meaning of "test do" syntax

I'm sorry if my question is silly because I just want to ask what does below line meaning in Ruby. (I'm reading a book about Rails as fast as possible for my course, so I don't have a firm grasp on the Ruby language.)
Here is a piece of code for unit test purpose:
class ProductTest < ActiveSupport::TestCase
test "product attributes must not be empty" do // this line I don't know
product = Product.new
assert product.invalid?
assert product.errors[:title].any?
assert product.errors[:description].any?
assert product.errors[:price].any?
assert product.errors[:image_url].any?
end
The thing I want to ask is: At the line I don't know, the syntax test "...." do, what does it mean? Is it a function, method, class, something else?
This stuff is called a class macro, fancy name for a simple mechanism:
It is a class method (def self.test), that way you can use it in you class definition directly for example.
The normal way to write test cases (in Test::Unit) would be more like this:
def test_something_interesting
...
end
However, ActiveSupport (part of Rails) provides you this syntactical sugar so that you can write it like this:
test "something interesting" do
...
end
This method will then define a method with the name test_something_interesting.
You can find the implementation in Rails:
activesupport/lib/active_support/testing/declarative.rb
It's a block. Somewhere in the testing framework this method is defined:
def test(description, &block)
# do something with block
end
I highly recommend that you pick a good Ruby book and that you read it slowly.

Resources