Как тестировать код, работающий с внешним АПИ. Заглушка на Синатре

Ситуация: в приложении вы опираетесь на внешний сервис для списаний с пластиковых карт. У сервиса крошечный АПИ: получить номер-маску карты по идентификатору пользователя, списать с указанного пользователя X рублей. Как проверить код, работающий с этим АПИ?

Конечно, можно не париться и реально дергать в тестах внешний сервис. Но это не круто. Во-первых, такие тесты будут тормозить. Во-вторых, такие тесты будут «мигать»: пропала сеть на мгновение, тест упал. В-третьих, это не всегда возможно: внешний сервис может не иметь песочницы или ограничивать количество запросов к ней.

Лучше в таких случаях использовать заглушки для внешних сервисов. Я использую четыре варианта заглушек, выбирая в зависимости от внешнего сервиса: свой или чужой, стабильный или часто меняющийся.

Один из вариантов — фейковый сервис на Синатре. Например, такой:

let(:fake_api) do
  Class.new Sinatra::Base do
    get "/users/:user_id/card" do
      content_type :json

      { number: "4111...1111" }.to_json
    end
  end
end

before do
  stub_request(:any, /api.nanocashier.com/).to_rack(fake_api)
end

В коде выше два интересных момента. Во-первых, Class.new с родителем Sinatra::Base в let, чтобы не добавлять глобальную константу из теста. Во-вторых, stub_request Вебмока, который роутит все запросы в rack-приложение.

Советую использовать заглушки на Синатре в случаях, когда нужно застабить внешний сервис, у которого нет собственных заглушек (например, stripe-ruby-mock), а сделать заглушку-адаптер не получается.

P. S. Ещё больше постов о программировании, тестах и культуре разработки у меня в Телеграме.