Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing & Mocking Services with Rspec

I created a Service Object called Transaction which processes an order made, sets up the payments and then sets up a model association.

The class is called Transaction with two methods, initialize and pay. I am testing it in spec/services/ (it is in app/services).

The initialize method takes in an account and some parameters passed in by the user to process the order.

I am trying to test pay with rspec. How do I actually do such a test? This function has a lot going on. For example, it creates new models, and then sets up some associations between them.

So far, I created a double account as follows:

@account = double("account", :confirmed => true, :confirmed? => true)

However, there are many functions (and associations) used in the Transaction pay method. So for example when these associations are called (once I call Transaction.pay in the test), it returns an error:

ActiveRecord::AssociationTypeMismatch:
Business(#70250016824700) expected, got RSpec::Mocks:.

The business model is a double just like account, and added as an attribute to @account. How can I test my Transaction.pay when it creates new models and associations in there?

Do I need to mock all of them as shown above? Or is there a better way to do it?

I am using FactoryGirl, but I am unable to use it as my account model uses Devise. Devise test helpers cannot be used outside of controllers, and since I'm testing in services, it won't work.

like image 335
darksky Avatar asked Jul 01 '13 11:07

darksky


1 Answers

It's hard to be specific, since you've not provided your Transaction class code, but at a high level, here's what I'd recommend:

  • Do not use FactoryGirl or Devise helpers - ideally, you're writing a unit test for this class, so you don't want any external dependencies.
  • Stub out any model instances you're dealing with - which is what you've done with account.
  • Stub out any external classes being used within the service (such as Business), and if you're creating instances of those from the classes, then they're doubles too.

Here's a vague example with no real implementation:

require './app/services/transaction'

describe Transaction do
  let(:transaction) { Transaction.new account, :foo => 'bar' }
  let(:account)     { double 'Account', :confirmed? => true }
  let(:business)    { double 'Business', :save => true }

  before :each do
    stub_const 'Business', double(:new => business)
  end

  describe '#pay' do
    it "does stuff" do
      # ...

      transaction.pay

      # ...
    end
  end
end

You'll note I've only required the transaction file - this helps you be aware of what other classes it depends on, because you'll (ideally) stub the constants, or if necessary, require other files. It'll keep this spec's running time much better too.

Also, I only stubbed confirmed?, not confirmed on the account double. From a Rails perspective they mean the same thing, but it's better to use one of them consistently, instead of both.

If you have a ton of setup (a lot of constants and test doubles), then I'd take that as a sign that this Transaction class probably needs to be broken up into several more specific classes.

like image 200
pat Avatar answered Sep 21 '22 15:09

pat