HTTP 테스트
번역일: 2026년 6월 20일
HTTP 테스트
소개
Laravel은 애플리케이션에 HTTP 요청을 보내고 응답을 검사하기 위한 직관적인 API를 제공합니다. 예를 들어, 아래와 같은 기능 테스트를 작성할 수 있습니다.
Pest
<?php
test('애플리케이션이 정상 응답을 반환한다', function () {
$response = $this->get('/');
$response->assertStatus(200);
});PHPUnit
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 기본 테스트 예시
*/
public function test_the_application_returns_a_successful_response(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}get 메서드는 애플리케이션에 GET 요청을 전송하고, assertStatus 메서드는 반환된 응답이 지정한 HTTP 상태 코드를 가지는지 검증합니다. 이 간단한 어서션 외에도, Laravel은 응답 헤더, 응답 본문, JSON 구조 등을 검사하기 위한 다양한 어서션을 제공합니다.
요청 만들기
테스트 내에서 get, post, put, patch, delete 메서드를 사용해 애플리케이션에 요청을 보낼 수 있습니다. 이 메서드들은 실제 네트워크 HTTP 요청을 발생시키는 것이 아니라, 내부적으로 요청 전체를 시뮬레이션합니다.
이 메서드들은 Illuminate\Http\Response 인스턴스 대신 Illuminate\Testing\TestResponse 인스턴스를 반환하며, 이를 통해 다양한 어서션을 체이닝하여 응답을 검사할 수 있습니다.
Pest
<?php
test('기본 요청', function () {
$response = $this->get('/');
$response->assertStatus(200);
});PHPUnit
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 기본 테스트 예시
*/
public function test_a_basic_request(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}일반적으로 각 테스트 메서드에서는 하나의 요청만 수행하는 것이 좋습니다. 하나의 테스트 메서드 내에서 여러 요청을 실행하면 예기치 않은 동작이 발생할 수 있습니다.
NOTE
테스트 실행 시 CSRF 미들웨어는 자동으로 비활성화됩니다.
요청 헤더 커스터마이징
withHeaders 메서드를 사용하면 요청을 보내기 전에 헤더를 커스터마이징할 수 있습니다. 원하는 임의의 헤더를 요청에 추가할 수 있습니다.
Pest
<?php
test('헤더와 함께 요청', function () {
$response = $this->withHeaders([
'X-Header' => 'Value',
])->post('/user', ['name' => '홍길동']);
$response->assertStatus(201);
});PHPUnit
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 헤더 커스터마이징 테스트 예시
*/
public function test_interacting_with_headers(): void
{
$response = $this->withHeaders([
'X-Header' => 'Value',
])->post('/user', ['name' => '홍길동']);
$response->assertStatus(201);
}
}쿠키
요청 전에 쿠키 값을 설정하려면 withCookie 또는 withCookies 메서드를 사용합니다. withCookie는 쿠키 이름과 값 두 개의 인수를 받고, withCookies는 이름/값 쌍의 배열을 받습니다.
Pest
<?php
test('쿠키와 함께 요청', function () {
$response = $this->withCookie('color', 'blue')->get('/');
$response = $this->withCookies([
'color' => 'blue',
'name' => '홍길동',
])->get('/');
//
});PHPUnit
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_interacting_with_cookies(): void
{
$response = $this->withCookie('color', 'blue')->get('/');
$response = $this->withCookies([
'color' => 'blue',
'name' => '홍길동',
])->get('/');
//
}
}세션 / 인증
Laravel은 HTTP 테스트 중 세션과 상호작용하기 위한 여러 헬퍼를 제공합니다. 먼저, withSession 메서드를 사용해 요청 전에 세션 데이터를 배열로 설정할 수 있습니다.
Pest
<?php
test('세션과 함께 요청', function () {
$response = $this->withSession(['banned' => false])->get('/');
//
});PHPUnit
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_interacting_with_the_session(): void
{
$response = $this->withSession(['banned' => false])->get('/');
//
}
}세션은 주로 현재 인증된 사용자의 상태를 유지하는 데 사용됩니다. actingAs 헬퍼 메서드는 특정 사용자를 현재 인증된 사용자로 손쉽게 지정할 수 있게 해줍니다. 예를 들어, 모델 팩토리를 사용해 사용자를 생성하고 인증할 수 있습니다.
Pest
<?php
use App\Models\User;
test('인증이 필요한 액션', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)
->withSession(['banned' => false])
->get('/');
//
});PHPUnit
<?php
namespace Tests\Feature;
use App\Models\User;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_an_action_that_requires_authentication(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->withSession(['banned' => false])
->get('/');
//
}
}actingAs 메서드의 두 번째 인수로 가드 이름을 전달하여 사용자 인증에 사용할 가드를 지정할 수도 있습니다. 지정한 가드는 해당 테스트가 실행되는 동안 기본 가드가 됩니다.
$this->actingAs($user, 'web')
응답 디버깅
테스트 요청을 수행한 후, dump, dumpHeaders, dumpSession 메서드를 사용해 응답 내용을 확인하고 디버깅할 수 있습니다.
Pest
<?php
test('기본 테스트', function () {
$response = $this->get('/');
$response->dumpHeaders();
$response->dumpSession();
$response->dump();
});PHPUnit
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 기본 테스트 예시
*/
public function test_basic_test(): void
{
$response = $this->get('/');
$response->dumpHeaders();
$response->dumpSession();
$response->dump();
}
}내용을 출력한 후 실행을 즉시 중단하려면 dd, ddHeaders, ddSession, ddJson 메서드를 사용합니다.
Pest
<?php
test('기본 테스트', function () {
$response = $this->get('/');
$response->ddHeaders();
$response->ddSession();
$response->ddJson();
$response->dd();
});PHPUnit
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 기본 테스트 예시
*/
public function test_basic_test(): void
{
$response = $this->get('/');
$response->ddHeaders();
$response->ddSession();
$response->dd();
}
}예외 처리
특정 예외가 발생하는지 테스트해야 할 때가 있습니다. 이를 위해 Exceptions 파사드를 통해 예외 핸들러를 페이크(fake)로 교체할 수 있습니다. 예외 핸들러를 페이크로 설정하면, assertReported와 assertNotReported 메서드를 사용해 요청 중 발생한 예외에 대해 어서션을 수행할 수 있습니다.
Pest
<?php
use App\Exceptions\InvalidOrderException;
use Illuminate\Support\Facades\Exceptions;
test('예외가 발생한다', function () {
Exceptions::fake();
$response = $this->get('/order/1');
// 예외가 발생했는지 어서션
Exceptions::assertReported(InvalidOrderException::class);
// 예외 내용에 대한 어서션
Exceptions::assertReported(function (InvalidOrderException $e) {
return $e->getMessage() === '주문이 유효하지 않습니다.';
});
});PHPUnit
<?php
namespace Tests\Feature;
use App\Exceptions\InvalidOrderException;
use Illuminate\Support\Facades\Exceptions;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 기본 테스트 예시
*/
public function test_exception_is_thrown(): void
{
Exceptions::fake();
$response = $this->get('/');
// 예외가 발생했는지 어서션
Exceptions::assertReported(InvalidOrderException::class);
// 예외 내용에 대한 어서션
Exceptions::assertReported(function (InvalidOrderException $e) {
return $e->getMessage() === '주문이 유효하지 않습니다.';
});
}
}assertNotReported와 assertNothingReported 메서드는 요청 중 특정 예외가 발생하지 않았거나 아무 예외도 발생하지 않았음을 어서션하는 데 사용할 수 있습니다.
Exceptions::assertNotReported(InvalidOrderException::class);
Exceptions::assertNothingReported();요청 전에 withoutExceptionHandling 메서드를 호출하면 해당 요청에 대한 예외 처리를 완전히 비활성화할 수 있습니다.
$response = $this->withoutExceptionHandling()->get('/');
PHP 언어나 사용 중인 라이브러리에서 deprecated된 기능을 애플리케이션이 사용하지 않는지 확인하려면 withoutDeprecationHandling 메서드를 호출하세요. Deprecation 처리가 비활성화되면 deprecated 경고가 예외로 변환되어 테스트가 실패합니다.
$response = $this->withoutDeprecationHandling()->get('/');
assertThrows 메서드를 사용하면 주어진 클로저 내 코드가 지정한 타입의 예외를 발생시키는지 어서션할 수 있습니다.
$this->assertThrows(
fn () => (new ProcessOrder)->execute(),
OrderInvalid::class
);발생한 예외를 직접 검사하고 싶다면, assertThrows의 두 번째 인수로 클로저를 전달하세요.
$this->assertThrows(
fn () => (new ProcessOrder)->execute(),
fn (OrderInvalid $e) => $e->orderId() === 123;
);JSON API 테스트
Laravel은 JSON API와 그 응답을 테스트하기 위한 여러 헬퍼도 제공합니다. json, getJson, postJson, putJson, patchJson, deleteJson, optionsJson 메서드를 사용해 다양한 HTTP 메서드로 JSON 요청을 보낼 수 있습니다. 데이터와 헤더도 함께 전달할 수 있습니다. 예를 들어, /api/user로 POST 요청을 보내고 기대하는 JSON이 반환되는지 테스트해 보겠습니다.
Pest
<?php
test('API 요청 테스트', function () {
$response = $this->postJson('/api/user', ['name' => '홍길동']);
$response
->assertStatus(201)
->assertJson([
'created' => true,
]);
});PHPUnit
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 기본 API 요청 테스트 예시
*/
public function test_making_an_api_request(): void
{
$response = $this->postJson('/api/user', ['name' => '홍길동']);
$response
->assertStatus(201)
->assertJson([
'created' => true,
]);
}
}또한 JSON 응답 데이터는 응답 객체에서 배열 변수처럼 접근할 수 있어 개별 값을 손쉽게 확인할 수 있습니다.
Pest
expect($response['created'])->toBeTrue();PHPUnit
$this->assertTrue($response['created']);NOTE
assertJson 메서드는 응답을 배열로 변환한 뒤, 주어진 배열이 JSON 응답 내에 존재하는지 확인합니다. 따라서 JSON 응답에 다른 속성이 더 있더라도, 주어진 프래그먼트가 포함되어 있으면 테스트는 통과합니다.
JSON 정확히 일치 어서션
앞서 설명한 것처럼 assertJson은 JSON의 일부 프래그먼트가 존재하는지 확인합니다. 반환된 JSON이 주어진 배열과 정확히 일치하는지 확인하려면 assertExactJson 메서드를 사용해야 합니다.
Pest
<?php
test('JSON 정확히 일치 어서션', function () {
$response = $this->postJson('/user', ['name' => '홍길동']);
$response
->assertStatus(201)
->assertExactJson([
'created' => true,
]);
});PHPUnit
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* JSON 정확히 일치 테스트 예시
*/
public function test_asserting_an_exact_json_match(): void
{
$response = $this->postJson('/user', ['name' => '홍길동']);
$response
->assertStatus(201)
->assertExactJson([
'created' => true,
]);
}
}JSON 경로 어서션
특정 경로에 원하는 데이터가 있는지 확인하려면 assertJsonPath 메서드를 사용합니다.
Pest
<?php
test('JSON 경로 값 어서션', function () {
$response = $this->postJson('/user', ['name' => '홍길동']);
$response
->assertStatus(201)
->assertJsonPath('team.owner.name', '김철수');
});PHPUnit
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* JSON 경로 값 테스트 예시
*/
public function test_asserting_a_json_paths_value(): void
{
$response = $this->postJson('/user', ['name' => '홍길동']);
$response
->assertStatus(201)
->assertJsonPath('team.owner.name', '김철수');
}
}assertJsonPath는 클로저도 인수로 받을 수 있어, 조건을 동적으로 판단할 수 있습니다.
$response->assertJsonPath('team.owner.name', fn (string $name) => strlen($name) >= 3);
Fluent JSON 테스트
Laravel은 JSON 응답을 유연하게 테스트할 수 있는 Fluent API도 제공합니다. assertJson 메서드에 클로저를 전달하면, 해당 클로저는 Illuminate\Testing\Fluent\AssertableJson 인스턴스를 받아 다양한 어서션을 체이닝할 수 있습니다. where로 특정 속성 값을 검증하고, missing으로 속성이 없음을 확인합니다.
Pest
use Illuminate\Testing\Fluent\AssertableJson;
test('Fluent JSON 테스트', function () {
$response = $this->getJson('/users/1');
$response
->assertJson(fn (AssertableJson $json) =>
$json->where('id', 1)
->where('name', '홍길동')
->where('email', fn (string $email) => str($email)->is('hong@example.com'))
->whereNot('status', 'pending')
->missing('password')
->etc()
);
});PHPUnit
use Illuminate\Testing\Fluent\AssertableJson;
/**
* Fluent JSON 테스트 예시
*/
public function test_fluent_json(): void
{
$response = $this->getJson('/users/1');
$response
->assertJson(fn (AssertableJson $json) =>
$json->where('id', 1)
->where('name', '홍길동')
->where('email', fn (string $email) => str($email)->is('hong@example.com'))
->whereNot('status', 'pending')
->missing('password')
->etc()
);
}etc 메서드 이해하기
위 예시에서 어서션 체인 마지막에 etc 메서드를 호출한 것을 볼 수 있습니다. 이 메서드는 JSON 객체에 어서션하지 않은 다른 속성이 있을 수 있음을 Laravel에 알립니다. etc를 사용하지 않으면, 어서션하지 않은 속성이 JSON 객체에 존재할 경우 테스트가 실패합니다.
이 동작의 목적은 JSON 응답에서 민감한 정보가 의도치 않게 노출되는 것을 방지하기 위함입니다. 모든 속성을 명시적으로 어서션하거나, etc를 통해 추가 속성을 허용하도록 강제합니다.
단, etc를 사용하지 않더라도 JSON 객체 내부에 중첩된 배열에 속성이 추가되는 것까지 막지는 않습니다. etc는 해당 메서드가 호출된 중첩 레벨에서만 추가 속성이 없음을 보장합니다.
속성 존재 / 부재 어서션
has와 missing 메서드를 사용해 속성이 존재하거나 존재하지 않음을 어서션할 수 있습니다.
$response->assertJson(fn (AssertableJson $json) =>
$json->has('data')
->missing('message')
);hasAll과 missingAll을 사용하면 여러 속성의 존재 또는 부재를 한 번에 어서션할 수 있습니다.
$response->assertJson(fn (AssertableJson $json) =>
$json->hasAll(['status', 'data'])
->missingAll(['message', 'code'])
);주어진 속성 목록 중 하나 이상이 존재하는지 확인하려면 hasAny 메서드를 사용합니다.
$response->assertJson(fn (AssertableJson $json) =>
$json->has('status')
->hasAny('data', 'message', 'code')
);JSON 컬렉션 어서션
라우트가 여러 항목(예: 사용자 목록)을 포함한 JSON 응답을 반환하는 경우가 많습니다.
Route::get('/users', function () {
return User::all();
});이런 경우, Fluent JSON 객체의 has 메서드를 사용해 응답에 포함된 항목 수와 내용을 어서션할 수 있습니다. first 메서드는 클로저를 받아 컬렉션의 첫 번째 객체에 대한 어서션을 수행합니다.
$response
->assertJson(fn (AssertableJson $json) =>
$json->has(3)
->first(fn (AssertableJson $json) =>
$json->where('id', 1)
->where('name', '홍길동')
->where('email', fn (string $email) => str($email)->is('hong@example.com'))
->missing('password')
->etc()
)
);JSON 컬렉션 어서션 범위 지정
라우트가 이름이 지정된 키를 가진 JSON 컬렉션을 반환하는 경우도 있습니다.
Route::get('/users', function () {
return [
'meta' => [...],
'users' => User::all(),
];
})이 경우, has 메서드를 통해 컬렉션의 항목 수와 내용을 모두 어서션할 수 있습니다.
$response
->assertJson(fn (AssertableJson $json) =>
$json->has('meta')
->has('users', 3)
->has('users.0', fn (AssertableJson $json) =>
$json->where('id', 1)
->where('name', '홍길동')
->where('email', fn (string $email) => str($email)->is('hong@example.com'))
->missing('password')
->etc()
)
);두 번의 has 호출 대신, 세 번째 인수로 클로저를 전달하면 하나의 호출로 간결하게 작성할 수 있습니다. 이 경우 클로저는 자동으로 컬렉션의 첫 번째 항목에 대해 실행됩니다.
$response
->assertJson(fn (AssertableJson $json) =>
$json->has('meta')
->has('users', 3, fn (AssertableJson $json) =>
$json->where('id', 1)
->where('name', '홍길동')
->where('email', fn (string $email) => str($email)->is('hong@example.com'))
->missing('password')
->etc()
)
);JSON 타입 어서션
JSON 응답의 속성이 특정 타입인지만 확인하고 싶을 때는 whereType과 whereAllType 메서드를 사용합니다.
$response->assertJson(fn (AssertableJson $json) =>
$json->whereType('id', 'integer')
->whereAllType([
'users.0.name' => 'string',
'meta' => 'array'
])
);| 문자를 사용하거나 타입 배열을 전달해 여러 타입 중 하나인지 확인할 수도 있습니다.
$response->assertJson(fn (AssertableJson $json) =>
$json->whereType('name', 'string|null')
->whereType('id', ['string', 'integer'])
);whereType과 whereAllType이 인식하는 타입: string, integer, double, boolean, array, null.
파일 업로드 테스트
Illuminate\Http\UploadedFile 클래스의 fake 메서드를 사용하면 테스트용 더미 파일이나 이미지를 생성할 수 있습니다. 이를 Storage 파사드의 fake 메서드와 함께 사용하면 파일 업로드 테스트가 크게 단순해집니다. 예를 들어, 아바타 업로드 폼을 아래처럼 테스트할 수 있습니다.
Pest
<?php
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
test('아바타를 업로드할 수 있다', function () {
Storage::fake('avatars');
$file = UploadedFile::fake()->image('avatar.jpg');
$response = $this->post('/avatar', [
'avatar' => $file,
]);
Storage::disk('avatars')->assertExists($file->hashName());
});PHPUnit
<?php
namespace Tests\Feature;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class ExampleTest extends TestCase
{