메인 콘텐츠로 건너뛰기
패키지에서 외부 API를 사용하고 싶을 때는 Laravel이 관리하는 HTTP 클라이언트 설정을 이어받는 것과 호출마다의 설정을 다음 호출로 넘기지 않는 것을 양립시킵니다. 독자적으로 Factory를 생성하거나 전송 전의 요청을 오랫동안 보관하면, 사용하는 쪽의 fake가 적용되지 않는 원인이 됩니다. 이 페이지는 Laravel Framework v13.39.0의 구현을 확인한 내용입니다. 기본 조작은 HTTP 클라이언트를, 검증 환경 준비는 패키지 테스트를 참조하세요.

Factory는 공유하고 PendingRequest는 매번 만든다

Laravel의 HTTP 클라이언트에는 서로 다른 책임을 가진 객체가 있습니다. FoundationServiceProvider는 Factory를 singleton으로 등록하고, Http 파사드도 같은 클래스 이름을 사용해 컨테이너에서 해석합니다. 따라서 일반적인 Laravel 애플리케이션에서는 컨테이너에서 받은 Factory를 통한 통신도 Http::fake()로 검증할 수 있습니다. 파사드를 직접 사용하는 설계도 가능하지만, 이 페이지에서는 의존 관계를 명시하기 위해 Factory를 주입합니다. Factory에 대한 baseUrl() 등 일반적인 요청 설정 메서드는 __call()을 통해 새로운 PendingRequest로 전달됩니다. createPendingRequest()는 생성 시점의 fake용 콜백과 stray request 제한을 넘기고, newPendingRequest()는 공통 미들웨어와 공통 옵션을 적용합니다.
패키지 안에서 new Factory()나 다른 HTTP 라이브러리의 클라이언트를 생성하면, 공유 Factory의 fake나 Http::preventStrayRequests()는 이어지지 않습니다. 이들은 프로세스 전체의 통신을 차단하는 기능이 아닙니다. Laravel용 클라이언트에서는 공유 Factory를 사용하고, 독립된 클라이언트를 제공하는 경우에는 그 클라이언트 전용 테스트 방법을 준비하세요.

API 조작을 작은 클라이언트로 정리하기

가상의 카탈로그 API에서 상품을 가져오는 Acme\Catalog 패키지를 예로 듭니다. src/CatalogClient.php는 Factory와 베이스 URL만 보관하고, 요청은 product() 실행 중에 만듭니다.
이 설계에서는 같은 클라이언트로 product(42, 'ja') 다음에 product(42)를 호출해도, 이전 호출에서 추가한 Accept-Language는 남지 않습니다. 사용하는 쪽 애플리케이션이 Factory에 설정한 공통 설정은 계속 각 요청에 적용됩니다.
  • 패키지 고유의 설정은 새로 만든 PendingRequest에 적용합니다. 패키지 초기화에서 Http::globalOptions()를 설정하면 관계없는 통신에도 영향을 주므로 피합니다.
  • 생성자에서는 전송하지 않습니다. 서비스 해석, Artisan 실행, 설정 파일 공개만으로 외부 API에 접속하지 않는 설계로 합니다.
  • throw()는 4xx·5xx를 RequestException으로 만듭니다. 접속 실패나 타임아웃의 ConnectionException과 구별하고, 실패를 빈 배열 등의 성공 값으로 바꾸지 않습니다.
  • json(flags: JSON_THROW_ON_ERROR)는 잘못된 JSON을 JsonException으로 만듭니다. 예제에서는 배열인지 여부만 검사하므로, 실제 API에서는 필수 필드나 데이터 타입도 검증합니다.
베이스 URL이나 헤더의 공통화만 필요하다면 공식 문서의 HTTP 매크로도 선택지입니다. API 조작, 반환값 검사, 예외 계약까지 패키지로 제공하는 경우에는 이런 전용 클래스로 나누면 유지보수하기 쉬워집니다. 매크로의 구조는 Macroable 트레이트를 참조하세요.

서비스 프로바이더에서 등록하기

패키지의 config/catalog.php에 초기 설정을 준비합니다. 여기에 HTTP 클라이언트나 클로저를 저장하지 말고, 설정 캐시에 저장할 수 있는 값만 둡니다.
src/CatalogServiceProvider.php는 설정을 병합하고, 클라이언트를 해석할 때 공유 Factory를 넘깁니다. Factory 자체의 바인딩은 덮어쓰지 않습니다.
프로바이더는 패키지 자동 검출 등으로 로드합니다. 사용자는 설정을 공개하고, config/acme-catalog.php의 베이스 URL을 실제 API로 변경합니다. 예제의 catalog.example.test는 설명과 fake용 가상 호스트입니다.
사용하는 쪽에서는 생성자 주입이나 컨테이너로 클라이언트를 해석합니다. 이 호출에서 처음으로 상품 조회 요청이 전송됩니다.
등록에는 bind()를 사용하지만, 클라이언트를 보관하는 쪽이 singleton이라면 그 참조까지 매번 새로 만들어지는 것은 아닙니다. 호출 고유의 언어, 인증 정보, 요청 객체를 프로퍼티에 저장하지 않는 것이 중요합니다. 자세한 내용은 패키지 서비스의 라이프사이클 설계를 참조하세요. 설정 덮어쓰기와 배포 시 캐시 갱신은 패키지 설정의 병합과 캐시에서 다룹니다. 베이스 URL은 신뢰할 수 있는 애플리케이션 설정으로 관리하고, 요청 입력으로 접속 대상을 자유롭게 지정하게 하지 마세요.

실제 클라이언트를 fake로 검증하기

클라이언트 자체를 모킹하면 공유 Factory를 사용하고 있는지, URL이나 헤더를 올바르게 조립하고 있는지는 확인할 수 없습니다. 패키지 테스트에서는 실제 CatalogClient를 컨테이너에서 해석하고, HTTP 응답만 fake로 만듭니다. 다음은 Orchestra Testbench의 테스트 환경에서 실행하는 예입니다. 각 테스트의 setUp()에서 preventStrayRequests()를 활성화합니다. 통신하는 테스트에서는 예상 URL에만 응답을 설정하고 '*' 폴백을 두지 않음으로써 URL의 오류도 검출할 수 있습니다.
두 번째 테스트에서는 클라이언트를 해석한 후에 fake를 설정하고 있습니다. Factory를 보관하고 실제 호출 시에 PendingRequest를 만들기 때문에 이 순서로도 fake가 반영됩니다. 생성자에서 PendingRequest를 생성·보관하는 설계로 변경하면, 생성 후에 추가한 fake가 반영되지 않는 것을 검출할 수 있습니다. assertSent()는 일치하는 요청이 1건 이상 있는지를 검증합니다. 의도하지 않은 추가 전송을 놓치지 않도록 assertSentCount()도 함께 사용합니다. assertSentInOrder()는 건수와 순서를 검증하므로, 같은 클라이언트를 연속으로 사용했을 때의 상태 분리 확인에도 활용할 수 있습니다. 가드 설정은 parent::setUp() 이후에 합니다. 이 테스트만으로 프로바이더 부팅 중의 통신까지 검증할 수 있는 것은 아니므로, 등록과 부팅에서는 통신하지 않는 설계를 유지합니다.
fake로 확인할 수 있는 것은 클라이언트가 조립한 요청과 준비한 응답에 대한 처리입니다. 실제 서비스의 인증, TLS 접속, 실제 타임아웃 시간, 외부 API의 사양 변경까지 보장하는 것은 아닙니다. 외부 접속을 수반하는 검증은 명시적으로 준비한 검증 환경에서 별도로 실시합니다.

장기 유지보수를 위해 추가할 검증

공유 Factory와 각 PendingRequest의 책임을 나눠 두면, 사용하는 쪽이 일반적인 Laravel 테스트 방법을 그대로 사용할 수 있습니다. 독자적인 fake 구조를 먼저 만들기보다, 우선 공유 Factory를 거치는 구현과 이를 확인하는 테스트를 준비하세요.

참조한 1차 정보

마지막 수정일 2026년 10월 27일