Neste meio tempo, um aspecto desta integração começou a me incomodar, então resolvi fazer este post para comentar sobre isto e também compartilhar partes do meu código a pedido de alguns usuários do post no face. Quem sabe agora eu recebo algum feedback!!! :-)
Meu modelo User estava com o seguinte código:
after_create :create_iugu_customer after_update :update_iugu_customer, if: 'name_changed? or email_changed?' private def create_iugu_customer iugu_customer = Iugu::Customer.create({ email: self.email, name: self.name }) self.update_column(:iugu_customer_id, iugu_customer.id) end def update_iugu_customer customer = Iugu::Customer.fetch(self.iugu_customer_id) customer.email = self.email customer.name = self.name customer.save end
Pode ser apenas paranóia minha, mas esse é um tipo de código que me incomoda, pois vou precisar de um usuário para muitos testes do meu sistema, porém para a grande maioria deles não preciso de um Iugu::Customer. Ter meu teste conversando com uma API externa também me incomoda, para este último caso posso utilizar o VCR, porém imagine adicionar o VCR a diversos testes para utilizar a rede sendo que você não precisa do Iugu::Customer, incoerente não?
Pensando nisso googlei um pouco e li sobre algumas alternativas, tal como desligar callbacks durante a execução dos testes, criar um camada de serviço, etc. A mais coerente para mim era criar uma camada de serviço, porém analisando os requisitos da aplicação achei que uma complexidade desnecessária. Optei por criar o Iugu::Customer apenas quando necessário.
Minha feature ficou então da seguinte forma:
require 'rails_helper' feature "Payment" do include ActiveJob::TestHelper let(:today) { Date.today } scenario "I should see a link to pay the service annuity", vcr: { cassette_name: 'iugu' } do # Given an event user = create(:user_confirmed) perform_enqueued_jobs do create_service(user) simulate_iugu_controller_webhook('invoice.created') end expect(page).to have_content('Você possui 30 dias para experimentar nosso sistema, aproveite!') expect(page).to have_content('Gostou do serviço? Contrate agora!') # When the user ask to contract the service click_link 'Contrate agora' # Then I can view my invoice with a payment link within '.box#invoices' do expect(page).to have_content('UUID') expect(page).to have_content('Anuidade serviço BLAH BLAH BLAH') expect(page).to have_content(I18n.l(today + 30.days)) expect(page).to have_content('Pendente') expect(page).to have_content('R$ 999,99') expect(page).to have_xpath("//a[@href='https://iugu.com/invoices/uuid']") end end private def simulate_iugu_controller_webhook(event, data = {}) data[:id] ||= 'INVOICE_UUID' data[:status] ||= 'pending' data[:subscription_id] ||= 'SUBSCRIPTION_UUID' Webhooks::Iugu::InvoiceCreationJob.perform_later(data) and return if event == 'invoice.created' Webhooks::Iugu::InvoiceUpdateStatusJob.perform_later(data) and return if event == 'invoice.status_changed' raise "Iugu event not recognized: #{event}" end end
No post do face falei que utilizei um request spec, porém posso simular o comportamento do controlador em um feature spec, como pode ser visto acima. Para gravar toda a interação com o VCR é necessário utilizar a opção "record: :new_episodes", completar o UUID retornado pelo iugu e ir executando os testes até ter todos os dados da interação. Além do próprio cassette você pode utilizar o painel de controle do iugu e o ngrok para conseguir mais detalhes da interação.
Observe no começo do cenário que utilizei o método perform_enqueued_jobs, pois após a criação do serviço eu executo um JOB para criação de uma Iugu::Subscription:
class Service < ActiveRecord::Base after_commit :schedule_subscription_creation, on: :create private def schedule_subscription_creation SubscriptionCreationJob.perform_later(self) end end
O conteúdo deste JOB é o seguinte:
class SubscriptionCreationJob < ActiveJob::Base queue_as :default def perform(service) @user = service.owners.first @user.iugu_customer_id.blank? ? create_iugu_customer : update_iugu_customer iugu_subscription = Iugu::Subscription.create({ plan_identifier: "basic_plan", customer_id: @user.iugu_customer_id, expires_at: 30.days.from_now, subitems: [{ description: "Anuidade do serviço BLAH BLAH BLAH", price_cents: '999999', quantity: 1, recurrent: true, }], custom_variables: [{ name: 'service_id', value: service.id }] }) service.create_subscription(iugu_id: iugu_subscription.id, iugu_attributes: iugu_subscription.attributes) end private def create_iugu_customer iugu_customer = Iugu::Customer.create({ email: @user.email, name: @user.name }) @user.update_column(:iugu_customer_id, iugu_customer.id) end def update_iugu_customer customer = Iugu::Customer.fetch(@user.iugu_customer_id) customer.email = @user.email customer.name = @user.name customer.save end end
Decidi criar a assinatura de forma assíncrona, pois não preciso dela imediatamente, além de garantir que uma possível falha de comunicação com o serviço externo não irá impedir o usuário de experimentar o serviço.
E olha quem mais encontramos aí: o código para criar/atualizar o Iugu::Customer. Com isso adiciono o VCR apenas aos testes que realmente precisam. Acredito que a parte de atualização do Iugu::Customer pode acabar voltando para o modelo User, sendo executada somente quando existir um iugu_customer_id, mas apenas a utilização do sistema dirá se isto será necessário.
Se tiver alguma dúvida e/ou sugestão deixa uma mensagem!