你的測試真的會打到實際 API 嗎?總有一天它會背叛你

Back
Category : News

Denis Augusto



測試壞掉了。而且不是你的錯。

星期一早上,你打開專案,CI 顯示紅燈。

你從上週五之後就沒動過任何東西。打開 log:與支付 API 的整合測試失敗。Timeout。

他們的 gateway 在凌晨 3 點發生不穩定。你的程式碼完美無缺。即便如此,build 還是紅燈,部署卡住,現在你得花半小時證明問題不是出在你身上。

有遇過這種情況嗎?依賴外部 API 的測試就是這樣:速度慢、不穩定,而且因為不是你的原因而壞掉。還有更糟的情境 — 真的去打支付 API 的測試,每次執行 php artisan test 就會在 sandbox 實際發動 30 筆收款,直到 gateway 覺得可疑而封鎖你。



問題:你的測試不應該認識網際網路

看看這個測試。看起來還算合理,對吧?

public function test_busca_o_cep(): void
{
    // isso bate no ViaCEP de VERDADE, toda vez que o teste roda
    $endereco = $this->service->buscarCep('01001000');

    $this->assertEquals('São Paulo', $endereco['cidade']);
}

Enter fullscreen mode

Exit fullscreen mode

哪裡有問題?所有測試中重要的事都有問題:

  • 速度慢 — 每個測試都要等待網路的來回。
  • 不穩定 — API 掛掉、變慢或改變回應,就會讓你的測試壞掉,但你的程式碼根本沒變。
  • 危險 — 如果是會收費、寄 email 或建立訂單的 API,你每次執行就是在真正執行這些動作。
  • 沒有控制權 — 要怎麼測試「當 API 回傳 500 錯誤時會發生什麼事」?你無法讓它在指定時間壞掉。

測試應該要驗證的是你自己的程式碼,而不是其他人的伺服器是否健康。



解決方案:Http::fake() 攔截一切

現在來到這個故事的 $pedido->restore() 部分。如果你使用 Laravel 的 HTTP Client(Http::get()Http::post()…),一行程式碼就能解決:

use IlluminateSupportFacadesHttp;

public function test_busca_o_cep(): void
{
    // A partir daqui, NENHUMA requisição sai de verdade.
    // Você diz o que cada URL deve responder.
    Http::fake([
        'viacep.com.br/*' => Http::response([
            'localidade' => 'São Paulo',
            'uf'         => 'SP',
        ], 200),
    ]);

    $endereco = $this->service->buscarCep('01001000');

    $this->assertEquals('São Paulo', $endereco['cidade']);
}

Enter fullscreen mode

Exit fullscreen mode

搞定。 Http::fake()攔截請求並回傳你指定的回應,完全不碰網路。測試可以在離線狀態下以毫秒級速度執行,而且每次結果都相同。你現在主宰了 API 的行為。



實際上該怎麼使用

fake() 非常有彈性。以下是你會經常使用到的幾種情境:

// Fingir QUALQUER requisição como 200 vazio (o mais simples)
Http::fake();

// Respostas diferentes por URL
Http::fake([
    'github.com/*'  => Http::response(['plano' => 'pro'], 200),
    'viacep.com.br/*' => Http::response('', 404), // simula CEP não achado
]);

// Simular a API fora do ar (erro 500) — o caminho que ninguém testa
Http::fake([
    'gateway.com/*' => Http::response('Erro interno', 500),
]);

Enter fullscreen mode

Exit fullscreen mode

注意最後一個:現在你可以測試失敗路徑了。「如果 gateway 回傳 500,我的應用程式是顯示友善訊息,還是直接在使用者面前崩潰?」如果沒有 fake(),你永遠無法回答這個問題。



光模擬還不夠:確認你送出的內容是正確的

這裡就是區分「真正測試」與「只是假裝」的陷阱所在。

模擬回應並不能保證你正確地組裝了請求。有沒有傳認證 header?有沒有把值放在正確的欄位?Http::assertSent() 可以檢查這些:

Http::fake();

$this->service->cobrar(valor: 150, cartao: 'tok_abc');

Http::assertSent(function (Request $request) {
    return $request->url() === 'https://gateway.com/charges'
        && $request->hasHeader('Authorization')
        && $request['amount'] === 150;
});

Enter fullscreen mode

Exit fullscreen mode

現在測試同時保證了兩邊:你正確地送出了該送的內容,而且正確地回應了收到的結果。



額外好處:禁止真實請求溜出去

有一個很陰險的細節。如果你忘記為某個 URL 設定 fake,請求就會真的發出去 — 而你根本不會發現那個測試仍然依賴網路。

preventStrayRequests() 可以把這扇門關起來:

Http::preventStrayRequests();

Http::fake([
    'gateway.com/*' => Http::response('ok'),
]);

Http::get('https://gateway.com/charges'); // ok, tá fakeado
Http::get('https://outra-api.com');        // 💥 lança exceção na hora

Enter fullscreen mode

Exit fullscreen mode

任何沒有對應 fake 的請求都會立刻拋出例外,而不是悄悄地跑到網路上。把這行放到你測試套件的 setUp() 裡,就能安心睡覺了。



在你關閉分頁之前

黃金法則:測試不應該和外面的世界對話。它應該快速且可預測地驗證你的程式碼。與 API 的真實對話,請留給獨立的整合測試,偶爾執行就好 — 而不是每次 commit 都跑。

一個重要細節:這只有在你使用 Laravel 的 HTTP Client(Http::)時才有效。如果你還是直接使用 Guzzle 或 cURL,fake() 就無法作用 — 而改用 Http:: 正是下一步(劇透:它也免費提供 timeoutretry,但那是另一篇文章的話題)。

告訴我:你在測試中會 mock API,還是仍然每天祈禱 sandbox 不要出問題?👀

https://dev.to/denisgusto1/seu-teste-bate-na-api-de-verdade-um-dia-ela-vai-te-trair-4l97

https://www.worldprogramming.org/posts/seu-teste-bate-na-api-de-verdade-um-dia-ela-vai-te-trair-lc1m6i