質問

We're in the process of adding PayPal Express Checkout as an option for checking out of an ecom app running on Rails 2.3.18. I've got the code working and some unit tests for my custom PayPal::Merchant::ExpressCheckout module, but I'm having trouble understanding how to mock or stub the controller methods properly so I can write integration tests.

One problem I'm facing is that all of the PayPal API calls are to the same endpoint URI with only an action parameter in the POSTed data to distinguish which action we're calling. While I've successfully setup FakeWeb to simulate proper XML responses from the PayPal API in my unit tests, there are some integration scenarios in which I need to be able to handle back-to-back API requests. Is there a way to tell FakeWeb to respond differently based on the POSTed data? Alternately, is there a way to have FakeWeb trigger a callback method after it intercepts the first request so I can setup the next request?

Another problem is how to mock the redirect to PayPal. Right now a user clicks the Checkout With PayPal button on our site which redirects them to a setup method on my ExpressCheckoutsController, which gets a token and sets up the checkout URL and then redirects the user there. I need to simulate two scenarios in my integration tests: 1. The user submits the form properly and is sent to my return URL 2. The user cancels and is sent to my cancel URL Is there a way to do this without rewriting the entire ExpressCheckoutsController class in the test file?

In case it matters, we're using the paypal-sdk-merchant gem. Our test environment uses the following gems:

group :test do
  gem 'autotest-rails', '4.1.0'
  gem 'ZenTest', '< 4.6'
  gem 'fakeweb', '1.2.6'
  gem 'mocha', '0.9.4'
  gem 'quietbacktrace', '0.1.1'
  gem 'factory_girl', '1.2.0'
  gem 'thoughtbot-shoulda', '2.10.2', :require => 'shoulda'
  gem 'nokogiri', '1.5.6'
  gem 'webrat', '0.4.4'
end

UPDATE

I was able to figure out the redirection issue by using Mocha to stub my custom ExpressCheckout module's express_checkout_url method so that it simply redirects to the return or cancel actions.

PayPal::Merchant::ExpressCheckout.any_instance.stubs(:express_checkout_url).returns(return_order_express_checkout_path)
役に立ちましたか?

解決

I learned that FakeWeb supports rotating responses, and while this helped in some integration test steps, I was still running into instances where GetExpressCheckoutDetails API method would be called an indeterminate number of times between the SetExpressCheckout and DoExpressCheckoutPayment calls, depending on several other factors. Rather than try to figure out how many responses I would need to fake, and after digging through a lot of the PayPal::SDK::Core library, I ended up with this helper method in my integration test class:

def stub_express_checkout
  api = PayPal::SDK::Merchant::API.new

  PayPal::Merchant::ExpressCheckout.any_instance.stubs(:express_checkout_url).returns(return_order_express_checkout_path)

  FakeWeb.register_uri(
    :post,
    api.service_endpoint,
    :content_type => "application/xml",
    :status => ["200", "OK"],
    :body => ""
  )

  PayPal::Merchant::ExpressCheckout.any_instance.stubs(:set_express_checkout).returns(
    PayPal::SDK::Merchant::DataTypes::SetExpressCheckoutResponseType.new(api.format_response({
      :response => FakeWeb.response_for(:post, api.service_endpoint).tap { |resp| 
        resp.instance_variable_set("@body", File.read(Rails.root.join("test/fixtures/express_checkout/success/set.xml")))
      }
    })[:data])
  )

  PayPal::Merchant::ExpressCheckout.any_instance.stubs(:get_express_checkout).returns(
    PayPal::SDK::Merchant::DataTypes::GetExpressCheckoutDetailsResponseType.new(api.format_response({
      :response => FakeWeb.response_for(:post, api.service_endpoint).tap { |resp| 
        resp.instance_variable_set("@body", File.read(Rails.root.join("test/fixtures/express_checkout/success/get.xml")))
      }
    })[:data])
  )

  PayPal::Merchant::ExpressCheckout.any_instance.stubs(:do_express_checkout).returns(
    PayPal::SDK::Merchant::DataTypes::DoExpressCheckoutPaymentResponseType.new(api.format_response({
      :response => FakeWeb.response_for(:post, api.service_endpoint).tap { |resp| 
        resp.instance_variable_set("@body", File.read(Rails.root.join("test/fixtures/express_checkout/success/do.xml")))
      }
    })[:data])
  )

  FakeWeb::Registry.instance.uri_map[FakeWeb::Registry.instance.send(:normalize_uri, api.service_endpoint)] = {}
end

It's ugly, and I don't like having to rely on so many internal PayPal::SDK::Core methods to stub my modules methods, but out of the 2 dozen approaches I tried, this is the one that finally worked.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top