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
Related
Background: I'm bringing to life a 6-year-old Rails project and haven't touched the framework since then. Thus, I'm re-learning many things.
I'm trying to understand the best approach to mock an API call that needs to be done synchronously. An Order has_one Invoice, and Invoice must get a reference from an external service. An Order is useless without an Invoice.
Below is a simple version of the application. The Order model is core to the application.
Open questions:
Is the best practise to globally mock SDKs in spec_helper.rb? Which would contain my allow_any_instance_of(InvoiceServiceSdk)
I have an Order factory, used almost everywhere in my tests. But I'm confused if I can loop in an Invoice factory as well. FactoryBot feels quite alien to me at the moment.
# app/models/order.rb
class Order < ApplicationRecord
has_one :invoice, autosave: true
before_create :build_invoice
def build_invoice
self.invoice = Invoice.new
end
end
# app/models/invoice.rb
class Invoice < ApplicationRecord
belongs_to :order
before_create :generate
def generate
invoice_service = InvoiceServiceSdk.new
self.external_id = invoice_service.fetch
end
end
# app/models/invoice_service_sdk.rb
require 'uri'
require 'net/http'
class InvoiceServiceSdk
def fetch
uri = URI('https://example.com/') # Real HTTP request
res = Net::HTTP.get_response(uri)
SecureRandom.urlsafe_base64 # "ID" that API "provides"
end
end
# spec/models/order.rb
require 'rails_helper'
RSpec.describe Order, type: :model do
before do
allow_any_instance_of(InvoiceServiceSdk).to receive(:fetch).and_return('super random external invoice ID')
end
context "new order + invoice" do
it {
o = Order.new
o.save
expect(o.invoice.external_id).to eq 'super random external invoice ID'
}
end
end
The rspec-mocks documentation discourages the use of allow_any_instance:
The rspec-mocks API is designed for individual object instances, but this feature operates on entire classes of objects. As a result there are some semantically confusing edge cases. For example, in expect_any_instance_of(Widget).to receive(:name).twice it isn't clear whether a specific instance is expected to receive name twice, or if two receives total are expected. (It's the former.)
Using this feature is often a design smell. It may be that your test is trying to do too much or that the object under test is too complex.
You can avoid it completely by just adding a factory method to your service objects:
class MyService
def intialize(**kwargs)
#options = kwargs
end
def call
do_something_awesome(#options[:foo])
end
def self.call(**kwargs)
new(**kwargs).call
end
end
allow(MyService).to recieve(:call).and_return([:foo, :bar, :baz])
Is it smelly to Stub the request in spec_helper?
Not necissarily. You can avoid a bit of overhead by refactoring the code as indicated above and stubbing the factory method. It also makes it so that your stubs are not coupled to the inner workings of the service object.
I would be more worried about the fact that this code does one thing right by using a service object and then immediately cancels that out by calling it in a model callback.
MyClass.inspect return incorrect class when I run whole test suite.
Problem:
I have User::CreditCard and ActiveMerchant::Billing::CreditCard classes in project. Last from activemerchant gem.
When I run single spec(rspec spec/models/user/credit_card_spec.rb) then it works correctly.
When I run whole suite(rspec spec) then spec fails with undefined method..., it doesn't matter. The problem is that in this case, my CreditCard class is not mine!!!
When I run single spec and do puts User::CreditCard.inpsect(or just p User::CreditCard, or in pry just User::CreditCard) then it returns User::CreditCard as expected.
When I run whole suite and do p User::CreditCard inside spec then it returns ActiveMerchant::Billing::CreditCard.
Background:
If you don't want to read "background" then be sure that there are NOTE in the end
I'm working with legacy code. So I don't fully know all parts of the image.
I want to create Value Object for credit card in my User. So I've create new tableless model(note the path and class name):
#app/models/user/credit_card.rb
class User::CreditCard
include ActiveModel::Model
delegate :card_number, :card_expiration, :card_type, to: :subscription
def initialize(subscription)
#subscription = subscription || Subscription.new
end
private
attr_reader :subscription
end
Of course I have User model:
#app/models/user.rb
class User
...
has_one :subscription
...
def credit_card
#credit_card ||= User::CreditCard.new(subscription)
end
end
My specs for user/credit_card:
#spec/models/user/credit_card_spec.rb
require 'spec_helper'
# require 'user/credit_card' # if I include this then it works correct
RSpec.describe User::CreditCard, type: :model do
let(:subscription) { build :subscription }
let(:credit_card) do
p User::CreditCard # this result depends on whole/not whole suite run...
# rspec spec => ActiveMerchant::Billing::CreditCard
# rspec spec/models/user => User::CreditCard
User::CreditCard.new(subscription)
end
it 'should delegate alowed messages to user subscription' do
%w[card_number card_expiration card_type].each do |attr|
expect(credit_card.public_send(attr)).to eql subscription.public_send(attr)
end
end
it 'disallow another methods' do
expect { credit_card.unexisted_method }.to raise_error(NoMethodError)
end
end
NOTE:
in spec I can require 'user/credit_card' and then it will work. But why it does not work without it?
Can it be a problem in another places? For example in controllers or somewhere else?
This is a glitch of rails autoloading + ruby constant resolution.
class C; end
CONST = 42
C::CONST
#⇒ (pry):3: warning: toplevel constant CONST referenced by C::CONST
#⇒ 42
Surprisingly enough, CONST was resolved. That is because of Ruby constant resolution algorithm.
One has two options to fix the problem: either to give a different name to the class User::CreditCard or to make sure it’s loaded. Otherwise Rails finds the constant CreditCard in ActiveMerchant::Billing namespace and is happy with using it.
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
I need to test whether an instance method gets called on a particular instance as a result of calling a class method. Something like:
class Dog < ActiveRecord::Base
def self.roll_trained
Dog.all.each { |d| d.rollover if d.trained? }
end
def rollover
# rollover and stuff
end
def trained?
self.trained == true
end
end
I've written a test like:
describe 'Dog.roll_trained' do
it 'rolls trained dogs' do
dog_1 = Dog.create(trained: true)
dog_1.should_receive(:rollover)
Dog.roll_trained
end
end
I thought I had this right, but the test fails. What am I doing wrong here?
The problem here is that dog_1 and the instances which are looped on the Dog.each is not the same instance.
This is because ActiveRecord generates a different instance for the same row (dog_1) in the database when you do Dog.each
I have written a blog post on this which you can read here: ActiveRecord and In-Memory Object State
One solution would be to stub Dog.all - Dog.stub(all: [dog_1])
Another solution is to save the trained attribute to the database and then spec against the database: dog_1.reload.trained.should be_true
In my model, I dynamically create some methods based on database records:
class Job < ActiveRecord::Base
belongs_to :job_status
# Adds #requisition?, #open?, #paused?, #closed?
class_eval do
JobStatus.all.each do |status|
unless method_defined? "#{status.name.downcase}?"
define_method("#{status.name.downcase}?") do
job_status_id == status.id
end
end
end
end
end
class JobStatus < ActiveRecord::Base
has_many :jobs
end
The job_statuses table contains some seed data, so is not going to be frequently changing, but in case I ever need to add new statuses, I don't have to add more code to get a boolean method for the new status.
However, I am not sure how to test these methods, because when rspec starts the job_statuses table is obviously empty, and when the JobStatus objects are created, Job gets initialized, but since no objects exist yet, it doesn't create any methods, and my tests fail because the methods don't exist.
Note that I am using rspec with spork & guard, and using database-cleaner with the truncation strategy (as per Railscast #257, since I'm using Selenium), so that probably complicates matters.
The solution I came up with was to abstract the creation of runtime methods out into a library file, and then in my test file, remove and redeclare my class before each test, and reload the actual class (and blueprints) at the end of the suite:
describe AssociationPredicate do
before(:all) do
["Continuous", "Standard"].each { |type| JobType.create!(:job_type => type) }
["Requisition", "Open", "Paused", "Closed"].each { |status| JobStatus.create!(:job_status => status) }
end
after(:all) do
DatabaseCleaner.clean_with :truncation, :only => %w( job_types job_statuses )
# Reload Job model to remove changes
Object.send(:remove_const, 'Job')
load 'job.rb'
load 'support/blueprints.rb'
end
before(:each) do
Object.send(:remove_const, 'Job')
# Redefine Job model for testing purposes
class Job < ActiveRecord::Base
belongs_to :job_type
belongs_to :job_status
has_many :job_applications
end
end
it "should add methods when included" do
Job.send(:association_predicate, :job_type)
job.should respond_to(:continuous?)
job.should respond_to(:standard?)
end
end
This way, I create a basic class for each test, add the runtime methods as necessarily, and return to the actual class when I'm done.
Try with enumerize gem. This make your status field like enumerator and build the "#{status.name.downcase}?" for your models. This gem came with it's own rspec-matchers making easiest your unit test.