Test simulator Mocking


Task Simulation

    Event Simulation
  • Scoped Event Simulation
  • Mail simulation
  • Notification simulation
  • Storage simulation
  • Facades
  • Introduction

    When testing a Laravel application, you may want to "simulate" the behavior of certain features of the application, thereby preventing that part from actually executing in the test. For example: an event (Event) will be triggered during the execution of the controller, thus preventing the event from actually being executed when testing the controller. This allows you to test only the controller's HTTP response without having to worry about triggering events. Of course, you can also test this event logic in a separate test.

    Laravel provides out-of-the-box helper functions for the simulation of events, tasks, and Facades. These functions are encapsulated based on Mocker and are very convenient to use. There is no need to manually call complex Mockery functions. Of course, you can also use Mockery or use PHPUnit to create your own emulator.

    Mock Object

    When mocking an object that will be injected into your application through Laravel's service container, you will The mock instance needs to be bound to the container as instance. This will tell the container to use a mock instance of the object instead of constructing the actual object:

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

    To make the above process more convenient, you can use the mock method provided by Laravel's base test case class :

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

    Task Simulation

    As an alternative to simulation, you can use Bus Facade's fake method prevents tasks from being actually distributed for execution. When using fake, assertions generally appear behind the test code:

    <?php
        namespace Tests\Feature;
        use Tests\TestCase;use App\Jobs\ShipOrder;
        use Illuminate\Support\Facades\Bus;
        use Illuminate\Foundation\Testing\RefreshDatabase;
        use Illuminate\Foundation\Testing\WithoutMiddleware;
        class ExampleTest extends TestCase{
            public function testOrderShipping()
                {   
                     Bus::fake();        
                     // 执行订单发货...        
                     Bus::assertDispatched(ShipOrder::class, function ($job) use ($order) { 
                                return $job->order->id === $order->id;      
                                 });       
                      // 断言任务并未分发...        
                      Bus::assertNotDispatched(AnotherJob::class);   
                    }
               }

    Event simulation

    As a mock Alternatively, you can use the fake method of the Event Facade to simulate the event listener. The event listener will not actually be triggered during testing. You can then test the assertion events running and even inspect the data they receive. When using fake, assertions generally appear behind the test code:

    <?php
        namespace Tests\Feature;
        use Tests\TestCase;use App\Events\OrderShipped;
        use App\Events\OrderFailedToShip;
        use Illuminate\Support\Facades\Event;
        use Illuminate\Foundation\Testing\RefreshDatabase;
        use Illuminate\Foundation\Testing\WithoutMiddleware;
        class ExampleTest extends TestCase{   
         /**
         * 测试订单发送
         */   
          public function testOrderShipping() 
             {   
                  Event::fake();        
                  // 执行订单发送...        
                  Event::assertDispatched(OrderShipped::class, function ($e) use ($order) {  
                            return $e->order->id === $order->id;    
                         });        
                   // 断言一个事件被发送了两次...   
                  Event::assertDispatched(OrderShipped::class, 2);        
                  // 未分配断言事件...        
                  Event::assertNotDispatched(OrderFailedToShip::class);   
                }
            }

    {note} Event monitoring will not be executed after calling Event::fake(). So, your event-based tests must use a factory model, for example, to create a UUID in the model's creating event, you should call Event::fake() after Use factory model.

    Simulating a subset of events

    If you only want to simulate event listeners for a specific set of events, you can pass them to fake or fakeFor Method:

    /**
     * 测试订单流程
     */
     public function testOrderProcess(){ 
        Event::fake([   
             OrderCreated::class,   
             ]);    
        $order = factory(Order::class)->create();    
        Event::assertDispatched(OrderCreated::class);    
       // 其他事件照常发送...    
      $order->update([...]);
     }

    Scoped event simulation

    If you If you only want to simulate event listening for part of the test, you can use the fakeFor method:

    <?php
        namespace Tests\Feature;
        use App\Order;use Tests\TestCase;
        use App\Events\OrderCreated;
        use Illuminate\Support\Facades\Event;
        use Illuminate\Foundation\Testing\RefreshDatabase;
        use Illuminate\Foundation\Testing\WithoutMiddleware;
        class ExampleTest extends TestCase{   
         /**
         * 测试订单流程
         */   
         public function testOrderProcess()   
          {     
             $order = Event::fakeFor(function () { 
                        $order = factory(Order::class)->create();            
                        Event::assertDispatched(OrderCreated::class);            
                        return $order;        
                 });       
               // 事件按正常方式发送,观察者将运行...       
              $order->update([...]);    
            }
          }

    Mail Simulation

    You can use the fake method of Mail Facade to simulate email sending. The email will not actually be sent during testing, and then you can assert Mailables are sent to users and they can even check what they have received. When using fakes, assertions are generally placed after the test code:

    <?php
        namespace Tests\Feature;
        use Tests\TestCase;use App\Mail\OrderShipped;
        use Illuminate\Support\Facades\Mail;
        use Illuminate\Foundation\Testing\RefreshDatabase;
        use Illuminate\Foundation\Testing\WithoutMiddleware;
        class ExampleTest extends TestCase{ 
           public function testOrderShipping()  
             {     
                Mail::fake();        
                // 断言没有发送任何邮件...        
                Mail::assertNothingSent();        
                // 执行订单发送...        
                Mail::assertSent(OrderShipped::class, function ($mail) use ($order) {  
                          return $mail->order->id === $order->id;       
                       });        
               // 断言一条发送给用户的消息...        
               Mail::assertSent(OrderShipped::class, function ($mail) use ($user) { 
                          return $mail->hasTo($user->email) &&                   
                          $mail->hasCc('...') &&                   
                          $mail->hasBcc('...');     
                       });        
                 // 断言邮件被发送两次...     
               Mail::assertSent(OrderShipped::class, 2);       
               // 断言没有发送邮件...        
               Mail::assertNotSent(AnotherMailable::class);   
               }
            }

    If you use a background task to execute the mail sending queue, you should use assertQueued instead of assertSent:

    Mail::assertQueued(...);
    Mail::assertNotQueued(...);

    Notification simulation

    You can use Notification Facade’s fake Method to simulate sending notifications. Notifications will not actually be sent during testing. You can then assert that notifications were sent to the user and even inspect what they received. When using fakes, assertions are generally placed after the test code:

    <?php
        namespace Tests\Feature;
        use Tests\TestCase;
        use App\Notifications\OrderShipped;
        use Illuminate\Support\Facades\Notification;
        use Illuminate\Notifications\AnonymousNotifiable;
        use Illuminate\Foundation\Testing\RefreshDatabase;
        use Illuminate\Foundation\Testing\WithoutMiddleware;
        class ExampleTest extends TestCase{
            public function testOrderShipping()
                {   
                     Notification::fake();        
                     // 断言没有发送通知...        
                     Notification::assertNothingSent();        
                     // 执行订单发送...        
                     Notification::assertSentTo(          
                       $user,            
                       OrderShipped::class,            
                       function ($notification, $channels) use ($order) {    
                                   return $notification->order->id === $order->id;   
                                      }       
                            );       
                       // 断言向给定用户发送了通知...      
                      Notification::assertSentTo(     
                            [$user], OrderShipped::class  
                              );       
                       // 断言没有发送通知...   
                      Notification::assertNotSentTo( 
                            [$user], AnotherNotification::class    
                              );       
                      // 断言通过 Notification::route() 方法发送通知...        
                       Notification::assertSentTo(     
                              new AnonymousNotifiable, OrderShipped::class     
                                );   
                          }
                 }

    ##Queue simulation

    As a simulation alternative, You can use the

    fake method of the Queue Facade to avoid actually putting the task in the queue for execution. You can then assert that tasks have been pushed to the queue and even inspect the data they received. When using fakes, assertions are generally placed after the test code:

    <?php
        namespace Tests\Feature;
        use Tests\TestCase;use App\Jobs\ShipOrder;
        use Illuminate\Support\Facades\Queue;
        use Illuminate\Foundation\Testing\RefreshDatabase;
        use Illuminate\Foundation\Testing\WithoutMiddleware;
        class ExampleTest extends TestCase{
            public function testOrderShipping() 
               {   
                    Queue::fake();        
                    // 断言没有任务被发送...        
                    Queue::assertNothingPushed();        
                    // 执行订单发送...        
                    Queue::assertPushed(ShipOrder::class, function ($job) use ($order) {  
                              return $job->order->id === $order->id;       
                             });        
                    // 断言任务进入了指定队列...        
                    Queue::assertPushedOn('queue-name', ShipOrder::class); 
                    // 断言任务进入2次...        
                      Queue::assertPushed(ShipOrder::class, 2);        
                      // 断言没有一个任务进入队列...        
                      Queue::assertNotPushed(AnotherJob::class);        
                      // 断言任务是由特定的通道发送的...        
                      Queue::assertPushedWithChain(ShipOrder::class, [    
                              AnotherJob::class,            
                              FinalJob::class        
                           ]);  
                       }
                   }

    ##Storage simulation

    You can use

    Storage

    The Facade's fake method can easily generate a simulated disk. Combined with the file generation tool of the UploadedFile class, it greatly simplifies file upload testing. For example:

    <?php
        namespace Tests\Feature;
        use Tests\TestCase;
        use Illuminate\Http\UploadedFile;
        use Illuminate\Support\Facades\Storage;
        use Illuminate\Foundation\Testing\RefreshDatabase;
        use Illuminate\Foundation\Testing\WithoutMiddleware;
        class ExampleTest extends TestCase{
            public function testAvatarUpload() 
               {   
                    Storage::fake('avatars');        
                    $response = $this->json('POST', '/avatar', [ 
                               'avatar' => UploadedFile::fake()->image('avatar.jpg')     
                  ]);        
              // 断言文件已存储...        
              Storage::disk('avatars')->assertExists('avatar.jpg');        
              // 断言文件不存在...        
              Storage::disk('avatars')->assertMissing('missing.jpg');   
              }
           }

    {tip} By default, the
    fake

    method will delete all files in the temporary directory. If you want to keep these files, you can use "persistentFake".

    Facades

    Unlike traditional static method calls, facades can also be simulated. Compared with traditional static methods, it has great advantages. Even if you use dependency injection, the testability is not inferior by half. In your tests, you may want to simulate calls to the Laravel Facade in your controller. For example, the behavior in the following controller:

    <?php
        namespace App\Http\Controllers;
        use Illuminate\Support\Facades\Cache;
        class UserController extends Controller{   
         /**
         * 显示应用里所有用户
         *
         * @return Response
         */    
         public function index() 
            {      
              $value = Cache::get('key');      
                //
             }
         }

    We can simulate the Cache Facade through the shouldReceive method. This function will return a Mockery instance. . Because Facade calls are actually managed by Laravel's service container, Facade can show better testability than traditional static classes. Next, let's simulate the get method of the Cache Facade:

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

    {note} You cannot simulate the Request Facade. Instead, use HTTP helper functions such as get and post if you need to pass in specific parameters when running your tests. Similarly, please simulate the Config Facade by calling Config::set during testing.

    This article was first published on the LearnKu.com website.