Тесты на интеграцию с платёжкой у нас на VCR. Кассет уже больше сотни, половина записана год назад, и когда провайдер поменял формат ответа, тесты остались зелёными, а прод упал на вебхуке. Думаю переходить на WebMock с явными заглушками, чтобы ответ был виден прямо в тесте:
stub_request(:post, %r{/v2/charges\z})
.with(body: hash_including(amount: 125000, currency: "RUB"))
.to_return(
status: 200,
headers: { "Content-Type" => "application/json" },
body: { id: "ch_1", status: "succeeded" }.to_json
)
Но так в каждом тесте по десять строк заглушек, а сценариев у нас под сорок. Как у вас устроено?
Кассеты стареют у всех, это свойство VCR, а не ваша ошибка. У него есть re_record_interval: ставите, например, 30 дней, и кассета перезаписывается при следующем прогоне, если есть доступ к песочнице провайдера. Изменившиеся кассеты потом смотрит человек в PR. Но если песочница нестабильная или её нет вовсе, WebMock честнее.
Я бы вообще не мокал HTTP в тестах бизнес-логики. Делается тонкий клиент к платёжке с двумя-тремя методами и рядом фейк с тем же интерфейсом. Бизнес-логика тестируется с фейком, а сам клиент отдельно, несколькими тестами на WebMock, где и проверяется формат запросов и ответов.
class FakePaymentClient
attr_reader :charges
def initialize
@charges = []
end
def charge(amount:, currency:, idempotency_key:)
@charges << { amount:, currency:, idempotency_key: }
{ "id" => "ch_#{@charges.size}", "status" => "succeeded" }
end
end
Подменяется через конфиг или аргумент конструктора, и десять строк заглушек превращаются в expect(client.charges.size).to eq(1). Сверять формат с реальным провайдером всё равно надо, но это уже пара тестов на клиент, а не сорок сценариев.
Фейк хорош, пока не начинает расходиться с настоящим клиентом. Видел, как в фейке метод принимал amount, а настоящий уже amount_cents, и тесты месяц были зелёные. Если идти этим путём, один и тот же набор контрактных тестов надо гонять и на фейке, и на настоящем клиенте с WebMock.