본문 바로가기

모킹

번역일: 2026년 6월 20일

모킹

소개

Laravel 애플리케이션을 테스트할 때, 특정 동작이 실제로 실행되지 않도록 "모킹(mocking)"하고 싶을 때가 있습니다. 예를 들어, 이벤트를 발생시키는 컨트롤러를 테스트할 때 이벤트 리스너가 실제로 실행되지 않도록 모킹할 수 있습니다. 이렇게 하면 이벤트 리스너의 실행을 신경 쓰지 않고 컨트롤러의 HTTP 응답만 집중적으로 테스트할 수 있습니다. 이벤트 리스너는 별도의 테스트 케이스에서 독립적으로 검증하면 됩니다.

Laravel은 이벤트, Job, 각종 파사드를 모킹하기 위한 편리한 헬퍼 메서드를 기본으로 제공합니다. 이 헬퍼들은 Mockery를 기반으로 하며, 복잡한 Mockery 메서드 호출을 직접 작성하지 않아도 되도록 편의 계층을 제공합니다.

객체 모킹

Laravel의 서비스 컨테이너를 통해 주입되는 객체를 모킹할 때는, 모킹된 인스턴스를 instance 바인딩으로 컨테이너에 등록해야 합니다. 이렇게 하면 컨테이너가 객체를 직접 생성하는 대신, 미리 등록해 둔 모킹 인스턴스를 사용하게 됩니다.

Pest

use App\Service; use Mockery; use Mockery\MockInterface; test('특정 동작을 모킹할 수 있다', function () { $this->instance( Service::class, Mockery::mock(Service::class, function (MockInterface $mock) { $mock->shouldReceive('process')->once(); }) ); });

PHPUnit

use App\Service; use Mockery; use Mockery\MockInterface; public function test_something_can_be_mocked(): void { $this->instance( Service::class, Mockery::mock(Service::class, function (MockInterface $mock) { $mock->shouldReceive('process')->once(); }) ); }

더 간결하게 작성하려면 Laravel 기본 테스트 클래스가 제공하는 mock 메서드를 사용하세요. 아래 예시는 위 코드와 동일하게 동작합니다.

use App\Service; use Mockery\MockInterface; $mock = $this->mock(Service::class, function (MockInterface $mock) { $mock->shouldReceive('process')->once(); });

객체의 일부 메서드만 모킹하고 나머지는 실제 로직을 그대로 실행하고 싶다면 partialMock 메서드를 사용하세요.

use App\Service; use Mockery\MockInterface; $mock = $this->partialMock(Service::class, function (MockInterface $mock) { $mock->shouldReceive('process')->once(); });

객체를 스파이(spy)하고 싶다면 spy 메서드를 사용할 수 있습니다. 스파이는 모킹과 유사하지만, 코드가 실행된 후에 상호작용이 있었는지 사후 검증(assertion)을 하는 방식으로 동작합니다.

NOTE

모킹(mock)은 호출이 일어나기 전에 기대값을 미리 설정하고, 스파이(spy)는 실제 코드가 실행된 후에 어떤 호출이 있었는지 검증합니다. 검증 시점이 다르다는 점을 기억하세요.

use App\Service; $spy = $this->spy(Service::class); // ... 테스트 코드 실행 ... $spy->shouldHaveReceived('process');

파사드 모킹

일반적인 정적 메서드 호출과 달리, Laravel의 파사드(실시간 파사드 포함)는 모킹이 가능합니다. 파사드는 서비스 컨테이너를 통해 관리되기 때문에, 의존성 주입을 사용하는 것과 동일한 수준의 테스트 편의성을 제공합니다.

예를 들어 아래와 같은 컨트롤러 액션이 있다고 가정해봅시다.

<?php namespace App\Http\Controllers; use Illuminate\Support\Facades\Cache; class UserController extends Controller { /** * 애플리케이션의 모든 사용자 목록을 반환합니다. */ public function index(): array { $value = Cache::get('key'); return [ // ... ]; } }

shouldReceive 메서드를 사용하면 Cache 파사드 호출을 모킹할 수 있습니다. 이 메서드는 Mockery 모킹 인스턴스를 반환합니다. 아래는 Cache 파사드의 get 메서드 호출을 모킹하는 예시입니다.

Pest

<?php use Illuminate\Support\Facades\Cache; test('index 조회', function () { Cache::shouldReceive('get') ->once() ->with('key') ->andReturn('value'); $response = $this->get('/users'); // ... });

PHPUnit

<?php namespace Tests\Feature; use Illuminate\Support\Facades\Cache; use Tests\TestCase; class UserControllerTest extends TestCase { public function test_get_index(): void { Cache::shouldReceive('get') ->once() ->with('key') ->andReturn('value'); $response = $this->get('/users'); // ... } }

WARNING

Request 파사드는 모킹하지 마세요. 대신 테스트를 실행할 때 get, post 같은 HTTP 테스트 메서드에 원하는 입력값을 직접 전달하세요. 마찬가지로 Config 파사드를 모킹하는 대신, 테스트 내에서 Config::set 메서드를 호출하여 설정값을 변경하세요.

파사드 스파이

파사드에 스파이를 적용하려면 해당 파사드의 spy 메서드를 호출하세요. 스파이는 모킹과 유사하지만, 코드 실행 후에 어떤 상호작용이 있었는지 사후 검증할 수 있습니다.

Pest

<?php use Illuminate\Support\Facades\Cache; test('값이 캐시에 저장된다', function () { Cache::spy(); $response = $this->get('/'); $response->assertStatus(200); Cache::shouldHaveReceived('put')->once()->with('name', 'Taylor', 10); });

PHPUnit

use Illuminate\Support\Facades\Cache; public function test_values_are_be_stored_in_cache(): void { Cache::spy(); $response = $this->get('/'); $response->assertStatus(200); Cache::shouldHaveReceived('put')->once()->with('name', 'Taylor', 10); }

시간 조작

테스트를 작성하다 보면 now() 또는 Illuminate\Support\Carbon::now()가 반환하는 현재 시간을 임의로 변경해야 할 때가 있습니다. Laravel의 기본 기능 테스트 클래스는 현재 시간을 자유롭게 조작할 수 있는 헬퍼 메서드를 제공합니다.

Pest

test('시간을 조작할 수 있다', function () { // 미래로 이동... $this->travel(5)->milliseconds(); $this->travel(5)->seconds(); $this->travel(5)->minutes(); $this->travel(5)->hours(); $this->travel(5)->days(); $this->travel(5)->weeks(); $this->travel(5)->years(); // 과거로 이동... $this->travel(-5)->hours(); // 특정 시점으로 이동... $this->travelTo(now()->subHours(6)); // 현재 시간으로 되돌아오기... $this->travelBack(); });

PHPUnit

public function test_time_can_be_manipulated(): void { // 미래로 이동... $this->travel(5)->milliseconds(); $this->travel(5)->seconds(); $this->travel(5)->minutes(); $this->travel(5)->hours(); $this->travel(5)->days(); $this->travel(5)->weeks(); $this->travel(5)->years(); // 과거로 이동... $this->travel(-5)->hours(); // 특정 시점으로 이동... $this->travelTo(now()->subHours(6)); // 현재 시간으로 되돌아오기... $this->travelBack(); }

시간 이동 메서드에 클로저를 전달하면 클로저가 실행되는 동안만 시간이 고정됩니다. 클로저 실행이 끝나면 시간은 자동으로 정상으로 복귀합니다.

$this->travel(5)->days(function () { // 5일 후의 시점에서 동작을 테스트... }); $this->travelTo(now()->subDays(10), function () { // 특정 시점에서 동작을 테스트... });

현재 시간을 완전히 고정하려면 freezeTime 메서드를 사용하세요. freezeSecond는 현재 초의 시작 시점에서 시간을 고정합니다. 두 메서드 모두 클로저를 받으며, 클로저 실행이 끝나면 시간이 정상으로 복귀합니다.

use Illuminate\Support\Carbon; // 시간을 고정하고 클로저 실행 후 복귀... $this->freezeTime(function (Carbon $time) { // ... }); // 현재 초의 시작 시점에 고정하고 클로저 실행 후 복귀... $this->freezeSecond(function (Carbon $time) { // ... })

이러한 시간 조작 메서드는 시간에 민감한 기능을 테스트할 때 매우 유용합니다. 예를 들어 게시판에서 일주일간 댓글이 없으면 스레드가 잠기는 기능을 아래와 같이 테스트할 수 있습니다.

Pest

use App\Models\Thread; test('게시글은 1주일 동안 활동이 없으면 잠긴다', function () { $thread = Thread::factory()->create(); $this->travel(1)->week(); expect($thread->isLockedByInactivity())->toBeTrue(); });

PHPUnit

use App\Models\Thread; public function test_forum_threads_lock_after_one_week_of_inactivity() { $thread = Thread::factory()->create(); $this->travel(1)->week(); $this->assertTrue($thread->isLockedByInactivity()); }

이 문서는 Laravel 공식 문서(MIT)를 한국 개발자를 위해 번역·재구성한 것입니다.

번역일: 2026년 6월 20일