Isolation test rails best practice - ruby-on-rails

I have a Rails application. Saying it's a Rails app that allow you to nurse some animals and we have an action to give some food to many animals in same time. To do that we have a class that iterate on each animal and call the #eat method. The eat method is a transition from the state starved to sated. This transition fails if the animal is already sated.
Example:
class Animal < ActiveRecord::Base
state_machine :state do
state :starved
state :sated
event :eat do
transition starved: :sated
end
end
end
class EatingService
attr_reader :error_models, :models
def new(models)
#error_models = []
#models = models
end
def process
ActiveRecord::Base.transaction do
models.each { |model| #error_models << model unless model.eat }
raise ActiveRecord::Rollback unless successfully_completed?
end
successfully_completed?
end
def successfully_completed?
error_models.empty?
end
end
Before adding the transaction, I could test it easily with mock objects.
Now, I know that I shouldn't use my Dog or Cat class because the EatingService class is not tied to any classes but how can I test that the rollback works well on a dummy objects?
PS: In this example, I only talk about Animal but in the real application I have totally different type of classes using the "EatingService", not only animals or these inherited classes.

In my opinion such design breaks OOP principle "Tell, Don't ask" so it's hard to isolate the test.
Eating service takes too much responsibilities which don't belong to them. Starving or not is not this class should care. All the service need to do is to ask the animal to eat. As to eat or not, that's the business of animal.
I suggest logic like below, moving the judgement to Animal.
# Eating service
def process
food = prepare_food
#animal.eat(food)
end
# Animal
def eat(food)
return false unless is_hungry? || like?(food)
chew(foo)
end
All animal needs to do is to respond to the method eat, which can be easily mocked. And the Serice's job ends with sending animal to eat.

Comment out all your code. Then write a test that files because one of your lines is not there. Repeat until you have code. Don't cheat and write the code first.
And your tests should use whatever objects they need. "Isolation" does not mean a test on a target class A is incapable of finding bugs in class B. Test isolation just means a test passing or failing depends on the fewest factors possible, including other tests, and including other target classes.
And try not to use mocks. I have seen projects slowed down despite having >1,000 test cases, because the tests abused mocks and often neglected to test the actual code.

You can check that ActiveRecord::Rollback was thrown:
it "fails if someone is sated" do
allow(ActiveRecord::Base).to receive(:transaction).and_yield
allow(subject.models[1]).to receive(:eat).and_return(false)
expect { subject.process }.to raise_error ActiveRecord::Rollback
end

Related

Globally mock method calling external API

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.

Rspec model callback

I have this model that my senior dev wrote:
class Thing < ActiveRecord::Base
after_commit on: :create do
SomeMobule.some_method(self)
end
end
I'm wondering how to test this callback.
I've known from the wise of the internet that you can do this:
(in model)
class Thing < ActiveRecord::Base
after_commit :do_something
def do_something
# doing stuff
end
end
(in spec)
it 'fires do_something after commit' do
expect(#instance).to receive(:do_something)
#instance.save
end
But I have no idea how to deal with this callback block.
Method name can be presented in symbol, easy, but what is another module's method name like in symbol? Or there's some other way to receive?
This might come from my lack of Ruby knowledge or for that matter general programming knowledge, and I have no idea even how to pursue the answer on the internet.
You can just test that SomeModule.some_method(self) is called.
let(:thing) { Thing.new }
it 'calls SomeModule.do_something after commit' do
expect(SomeModule).to receive(:do_something).with(thing)
thing.save
end
Which is fine if SomeModule.do_something is an application boundary such as a client to an external API.
If its not the test is very low value from a BDD standpoint - it only tests how the pieces are glued together - not the actually behaviour. A better test would be to test that the expected behaviour is triggered when you save the model.
# a really contrived example
it 'becomes magical when it is saved' do
expect do
thing.save
thing.reload
end.to change(thing, :magical).from(false).to(true)
end

Organizing API-Calls in callbacks

We are maintaining several Rails-Apps which all pose a similar problem that we don't have a really good solution to: All these apps contain models that need to make a API-Call to an external service in their lifecycle.
Possible cases:
User is subscribed to a Newsletter-subscriber-list, when successfully created
Prices for an offer are synced with an external shopping-system after updating
Product is updated in the Search-Index after updating
What we exprienced to NOT be a good solution: Adding these calls to the after_*callbacks of the model. Since that breaks tests fast, cause all factories now have to deal with the api-calls.
I'm looking for a good way to organize these API-call. How do you guys do this?
Ideas we came up with, which I considered not real ideal:
Moving those callbacks to the controller. Now they get easily forgotten, when creating an object
Spawning an asynchronous worker to handle the api-call. Then every - even small app - needs to have the overhead of a delayed job-queue, like sidekiq.
If you are concerned about testing you could put the callback methods into a separate class and mock the callback class during testing. Here's an example using RSpec, given the following Foo and FooCallbacks classes:
class Foo < ActiveRecord::Base
after_save FooCallbacks
end
class FooCallbacks
def self.after_save
fail "Call to external API"
end
end
You can write and successfully run a spec like this:
describe Foo do
before do
allow(FooCallbacks).to receive(:after_save)
end
it "should not invoke real APIs" do
Foo.create
end
end
This is how I now did it, after the advise:
In Foo:
class Foo < ActiveRecord::Base
before_save Foo::DataSync
end
Foo:DataSynclooks like this:
class Foo::DataSync
def self.before_save(foo)
...do the API-Calls...
end
end
Now for testing in rspec I added this:
To spec_helper.rb:
config.before(:each) do
Foo::DataSync.stub(:before_save)
end
Note that config.before(:suite) will not work, since Foo:DataSync is not loaded at that time.
Now foo_spec.rb contains just this:
describe Foo do
let(:foo) {create(:foo)}
it "will sync its data before every save" do
expect(Foo::DataSync).to receive(:before_save).with(foo)
foo.save
end
end
The Foo::DataSync can be tested like this:
describe Foo::DataSync do
let!(:foo) {create(:foo)}
before do
Foo::DataSync.unstub(:before_save)
end
after do
Foo::DataSync.stub(:before_save)
end
describe "#before_save" do
...my examples...
end
end

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.

Repository or Gateway pattern in Ruby

How can I implement the Repository or Gateway pattern in Ruby?
I come from a C# world and I usually abstract away my data access but with ActiveRecord as the default data access mechanism in Ruby, it's not obvious how to accomplish that.
What I usually would do in C# is work with abstract interfaces and then have a concrete implementation for EFCustomerRepository, NHibernateCustomerRepository and InMemoryCustomerRepository and depending on the situation I inject the matching concrete implementation.
So now, what’s the Ruby way?!
As far as I understand it, in dynamic languages you would not need something like DI (dependency injection).
And Ruby has powerful language features to allow things like mixins.
But you would define the mixin to use statically on class or module-level?
How do I write my business logic if I want to develop against an in-memory repository and in production I would switch to my ActiveRecord-Repository?
If might be on the wrong path here since I'm used to thinking in a statically typed language. How would someone tackle this task the Ruby way? Basically I want to make my persistence layer abstract and it's implementations interchangeable.
EDIT: I am referring to robert c. martins (unclebob) keynote about architecture
Thanks for any help...
I get what you are saying. I come from a .NET background as well. Abstracting away your business logic & persistance logic is imo a good idea. I haven't found a gem that does it for you yet. But you can easily roll something simple yourself. In the end a repository pattern is basically a class that delegates to your persistance layer.
Here is what I do:
require 'active_support/core_ext/module/attribute_accessors'
class GenericRepository
def initialize(options = {})
#scope = options[:scope]
#association_name = options[:association_name]
end
def self.set_model(model, options = {})
cattr_accessor :model
self.model = model
end
def update(record, attributes)
check_record_matches(record)
record.update_attributes!(attributes)
end
def save(record)
check_record_matches(record)
record.save
end
def destroy(record)
check_record_matches(record)
record.destroy
end
def find_by_id(id)
scoped_model.find(id)
end
def all
scoped_model.all
end
def create(attributes)
scoped_model.create!(attributes)
end
private
def check_record_matches(record)
raise(ArgumentError, "record model doesn't match the model of the repository") if not record.class == self.model
end
def scoped_model
if #scope
#scope.send(#association_name)
else
self.model
end
end
end
And then you could for example have a Post repository.
class PostRepository < GenericRepository
set_model Post
# override all because we also want to fetch the comments in 1 go.
def all
scoped_model.all(:include => :comments)
end
def count()
scoped_model.count
end
end
Just instantiate it in your controller in a before_filter or initialize or wherever. In this case I'm scoping it to the current_user so that it only fetches those records and automatically create posts only for the current user.
def initialize
#post_repository = PostRepository.new(:scope => #current_user, :association_name => 'posts')
end
def index
#posts = #post_repository.all
respond_with #posts, :status => :ok
end
I came across https://github.com/bkeepers/morphine which is a tiny DI framework. It could work for you :) But DI isn't a heavily used pattern in ruby. Also, I instantiate my repos in order to scope them to a current user or something else.
I'm on a quest to find the right way to do just what you ask and do a little write-up about it if I ever do find it. But for now it's already sufficient to make the clean cut between persistance & my controllers. If this is done properly it won't be a big hassle to switch to a different system later on. Or add caching etc.
Well, ActiveRecord already provides abstract persistence layer - it has several different adapters allowing it to use different database backends. Also, it's open-source so you are free to take a look at how it has been achieved.
Upon the first glance you can see that it also has an AbstractAdapter that all other adapters inherit, however, as Ruby is dynamic, duck-typing language, AbstractAdapter doesn't have to contain abstract methods which will be overridden in children classes, neither defines a "contract" that they should honour.
Edit:
Here's a simple sketch on how you could abstract away your storage in Ruby, not sure which pattern exactly it is:
# say you have an AR model of a person
class Person < ActiveRecord::Base
end
# and in-memory store of persons (simply, a hash)
IN_MEMORY_STORE = {
:Person => ['Tim', 'Tom', 'Tumb']
}
# this will abstract access
class MyAbstractModel
def initialize item, adapter
#item = item
#adapter = adapter
end
# get all elements from the store
def all
case #adapter
when :active_record
# pull from database:
Object.const_get(#item).all
when :in_memory_store
# get from in-memory store
IN_MEMORY_STORE[#item]
else
raise "Unknown adapter"
end
end
end
# get all Persons from in-memory storage...
p MyAbstractModel.new(:Person, :in_memory_store).all
# ...and from a database
p MyAbstractModel.new(:Person, :active_record).all
#serverinfo, I don't know much about C#. But when I came to Ruby from a Java/C background, I was blown away when I realized how flexible this language really is. You say that your real problem here is to "abstract away your persistence layer and make it exchangeable". You also asked "how will I write the business logic".
I suggest that you throw away your preconceptions and ask yourself: "how would I like to express data access/storage within my business logic layer"? Don't worry about what you think can or can't be done; if you can figure out how you would like the interface to work, there is probably a way it can be done in Ruby.
You will also have to decide how you want to specify the concrete implementation to be used. Is it possible you will want to use a different data store for different model objects? Might you want to switch at run-time? Would you like to specify the backend to be used in a configuration file, or in code? If you can decide what you want to do, there are lots of people on Stack Overflow who can help you figure out how to do it.

Resources