I refactored credit card payments on an application that uses the ActiveMerchant gem. I was not confident in the test, so, I re-wrote the tests to verify responses from Braintree. Once I verified I did not break anything with my refactor, I decided the best next step was to remove the interaction with Braintree and replace it with stubbed responses. I always forget when and how to use RSpec allow, spy and double methods. This post is meant to help reinforce my knowledge of the aforementioned methods.
1234567891011121314151617
describeCreditCardPaymentdolet!(:payment){build(:credit_card_payment)}describe'#authorize'dobefore{payment.authorize}it'records the GatewayTransaction'dotransaction=GatewayTransaction.lastexpect(transaction.success).toeqtrueexpect(transaction.amount).toeqamountexpect(transaction.message).toeq'1000 Approved'expect(transaction.response).tobe_presentexpect(transaction.service_fee).toeqservice_fee[:service_fee]endendend
Authorize is a wrapper around the payment gateway #authorize method which returns a response from Braintree. I should have confidence that when sending Braintree arguments that the#authorize expects, the response will be successful. This is interaction is what needs to be stubbed in order to not call the Braintree API in the test environment. The benefits of this approach are that the test will be faster and more resilient because there is no communication with an external service. The negatives of this approach is that the API can change.
Let’s update the above codeblock to stub out the interaction with Braintree.
1234567891011121314151617181920212223242526
describeCreditCardPaymentdolet(:gateway_double){double('ActiveMerchant::Billing::BraintreeGateway')}let!(:payment){build(:credit_card_payment,gateway:gateway_double)}before{payment.gateway=gateway_double}describe'#authorize'doit'sends a #authorize request to Braintree'doexpect(gateway_double).toreceive(:authorize).exactly(1).times.with(amount,payment.credit_card,payment.credit_card_descriptor).and_return(authorize_response_stub)payment.authorizeendit'creates a GatewayTransaction'doexpect{payment.authorize}.tochange(GatewayTransaction,:count).by(1)endenddefauthorize_response_stubparams={'response_from_braintree'=>'yay'}ActiveMerchant::Billing::Response.new(true,'1000 Approved',params,{authorization:'3e4r5q'})endend
Notice that in the it block the expectation comes before the #payment method call. We could replace the double with a spy. The difference is a spy uses the have_received method. It is your preference to use either a double or a spy.
123456789101112131415161718
let(:gateway_double){spy('ActiveMerchant::Billing::BraintreeGateway')}let!(:payment){build(:credit_card_payment,gateway:gateway_double)}before{payment.gateway=gateway_double}describe'#authorize'doit'sends a #authorize request to Braintree'dopayment.authorizeexpect(gateway_double).tohave_receive(:authorize).exactly(1).times.with(amount,payment.credit_card,payment.credit_card_descriptor).and_return(authorize_response_stub)endit'creates a GatewayTransaction'doexpect{payment.authorize}.tochange(GatewayTransaction,:count).by(1)endend