Fake implementation of API library at runtime (development mode)

94 views
Skip to first unread message

Steven Ringo

unread,
Nov 13, 2012, 6:56:23 PM11/13/12
to rails-...@googlegroups.com
We have a requirement to talk to a third party payment provider via an http API
While I am happy with the testing of this implementation and mocking these calls where necessary, I am looking for a good way to do this in the development environment.

The interactions with the provider have significant implications for the UI that its unfeasible to test in an automated fashion.

I don't want to use VCR or mock the web requests, as this is too low level and the exact implementation has not been finalised. This doesn't mean the UI can't be tweaked and tested though, and it would be good to have an implementation that can be changed to emit the desired response by for example, changing a flag.

Here's where I am:

I have a class that proxies calls from the app to the API, and in testing its trivial to mock the calls to Acme.new

class Acme
  def pay_money(amount)
    AcmeAPI::PayMoneyRequest.new({
      type: "Ca$h",
      subscriber: @subscriber
      amount: amount
    })
  end
end

class FakeAcme
  def pay_money(amount)
    canned_result = { invoice_number: "12345" }
  end
end

Assuming my app uses the Acme class as such:

class PurchaseProduct
  def buy_it_now
    acme = Acme.new(subscriber)
    result = acme.pay_money(product.price)
  end
end

If I want to replace Acme with FakeAcme, what is the best way to do this?

In another programming (cough) life, I probably would have done something like:

class PurchaseProduct(acme_provider)
  def buy_it_now
    acme = acme_provider.new(subscriber)
    result = acme.pay_money
  end
end

and then use some sort of DI library to specify the acme_provider at runtime.

I could do something like in the rails initializer to hack it too:

if Rails.env.development?
  Acme = FakeAcme
end

Any comments/ideas on this approach or examples of alternatives would be great.

Thanks,

Steve

Simon Russell

unread,
Nov 13, 2012, 7:14:13 PM11/13/12
to rails-...@googlegroups.com
I generally do something in between your DI and const-hack approach.  Usually something like some sort of config class that can be asked for the payment provider.  At least then your hack is not going to confuse people as much.

In this sort of situation I'd almost always be suspicious that there'll come a time when two payment providers are used in the system at once, or at least that switching to another one will happen.  So I also try and reduce the number of references to the config thing, even if that's just through some factory type thing.  Although clearly at this stage you're not going to know what will govern the choice between two payment providers, so it's not really possible to make it any cleaner.

Not all "other programming (cough)" patterns are bad, although I'd say DI is less relevant (and frequently used in lieu of a proper solution).


--
You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
To view this discussion on the web visit https://groups.google.com/d/msg/rails-oceania/-/Vou8eHbj5SAJ.
To post to this group, send email to rails-...@googlegroups.com.
To unsubscribe from this group, send email to rails-oceani...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/rails-oceania?hl=en.

Steve Hoeksema

unread,
Nov 13, 2012, 7:19:28 PM11/13/12
to rails-...@googlegroups.com
fogs' Mock objects might be of interest

Sebastian Porto

unread,
Nov 13, 2012, 7:37:56 PM11/13/12
to rails-...@googlegroups.com
I think DI is a nice way to go. Combined with some constants in an initializer.

    class PurchaseProduct
        def buy_it_now

            acme = acme_provider.new(subscriber)
            result = acme.pay_money
        end
        
        def acme_provider
            @acme_provider ||= ACME_PROVIDER
        end
        
        def acme_provider=(obj)
            @acme_provider = obj
        end
    end
 

    if Rails.env.development?
        ACME_PROVIDER  = FakeAcme
    else
        ACME_PROVIDER  = Acme
    end


It is also very easy to swap the dependency in the test:

    pp = PurchaseProduct.new
    pp.acme_provider = FakeAcme


Sebastian



Jon Rowe

unread,
Nov 14, 2012, 4:39:21 AM11/14/12
to rails-...@googlegroups.com
Not overly relevant but I like how OmniAuth allows you to put it into test mode so it will return fake data rather than hitting providers. Very useful in development mode!

Also I tend to configure my APIs on a per environment basis, and have those settings in the relevant environment/#{env}.rb file. I find this to be a lot more flexible/cleaner than littering code with Rails.env.environment? calls.

Jon Rowe
-----------------------------

--
You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.

Simon Russell

unread,
Nov 14, 2012, 5:58:38 PM11/14/12
to rails-...@googlegroups.com
The not using Rails.env.development? call is certainly a good idea, I usually use a config yaml file, rather than the config/environment things (that way I can configure it in production with capistrano or chef, depending on need).


On Thu, Nov 15, 2012 at 8:04 AM, jamesl <ladd....@gmail.com> wrote:
Did this sort of thing yesterday, adding to config/initializers to setup the default provider and then in
config/environments overriding as necessary.


--
You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
To view this discussion on the web visit https://groups.google.com/d/msg/rails-oceania/-/xwx705wwVw0J.
Reply all
Reply to author
Forward
0 new messages