Skip to content

Latest commit

 

History

History
499 lines (354 loc) · 18.1 KB

testing.md

File metadata and controls

499 lines (354 loc) · 18.1 KB

Testing

Introduction

Laravel is built with unit testing in mind. In fact, support for testing with PHPUnit is included out of the box, and a phpunit.xml file is already setup for your application. The framework also ships with convenient helper methods allowing you to expressively test your applications.

An ExampleTest.php file is provided in the tests directory. After installing a new Laravel application, simply run phpunit on the command line to run your tests.

Test Environment

When running unit tests, Laravel will automatically set the configuration environment to testing. Also, Laravel includes configuration files for session and cache in the test environment. Both of these drivers are set to array while in the test environment, meaning no session or cache data will be persisted while testing. You are free to create other testing environment configurations as necessary.

The testing environment variables may be configured in the phpunit.xml file.

Defining & Running Tests

To create a test case, simply create a new test file in the tests directory. The test class should extend TestCase. You may then define test methods as you normally would when using PHPUnit.

An Example Test Class

<?php


class FooTest extends TestCase
{
	public function testSomethingIsTrue()
	{
		$this->assertTrue(true);
	}
}

You may run all of the tests for your application by executing the phpunit command from your terminal.

Note: If you define your own setUp method within a test class, be sure to call parent::setUp.

Application Testing

Laravel provides a very fluent API for making HTTP requests to your application, examining the output, and even filling out forms. For example, take a look at the ExampleTest.php file included in your tests directory:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->visit('/')
             ->see('Laravel 5');
    }
}

The visit method makes a GET request into the application. The see method asserts that we should see the given text should be contained in the response returned by the application. This is the most basic application test available in Laravel.

Disabling Middleware

When testing your application, you may find it convenient to disable middleware for some of your tests. This will allow you to test your routes and controller in isolation from any middleware concerns. Laravel includes a simple WithoutMiddleware trait that you can use to automatically disable all middleware for the test class:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
	use WithoutMiddleware;

    //
}

If you would like to only disable middleware for a few test methods, you may use the withoutMiddleware method:

<?php

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
    	$this->withoutMiddleware();

        $this->visit('/')
             ->see('Laravel 5');
    }
}

Interacting With Your Application

Of course, you can do much more than simply assert that text appears in a given response. Let's take a look at some examples of clicking links and filling out forms:

Clicking Links

In this test, we will make a request to the application, "click" a link in the returned response, and then assert that we landed on a given URI. In this example, let's assume there is a link in our response that has a text value of "About Us". For example:

<a href="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/about-us">About Us</a>

Now, let's write a test that clicks the link and asserts the user is on the correct page:

public function testBasicExample()
{
    $this->visit('/')
         ->click('About Us')
         ->seePageIs('/about-us');
}

Working With Forms

Laravel also provides several methods for testing forms such as type, select, check, attach, and press. Let's take a look at each of these methods using an example. First, let's imagine this form exists on the application's registration page:

<form action="/register" method="POST">
	{{ csrf_field() }}

	<div>
		Name: <input type="text" name="name">
	</div>

	<div>
		<input type="checkbox" value="yes" name="terms"> Accept Terms
	</div>

	<div>
		<input type="subnit" value="Register">
	</div>
</form>

Now we can write a test to complete this form and inspect the result:

public function testNewUserRegistration()
{
    $this->visit('/register')
         ->type('Taylor', 'name')
         ->check('terms')
         ->press('Register');
         ->seePageIs('/dashboard');
}

Of course, if your form contains other inputs such as radio buttons or drop-down boxes, you may easily fill out those types of fields as well. Here is a list of each form manipulation method:

Method Description
$this->type($text, $elementName) "Type" text into a given field.
$this->select($value, $elementName) "Select" a radio button or drop-down field.
$this->check($elementName) "Check" a checkbox field.
$this->attach($pathToFile, $elementName) "Attach" a file to the form.
$this->press($buttonTextOrElementName) "Press" a button with the given text or name.

Working With Attachments

If your form contains file input types, you may attach files to the form using the attach method:

public function testPhotoCanBeUploaded()
{
    $this->visit('/upload')
         ->name('File Name', 'name')
         ->attach($absolutePathToFile, 'photo')
         ->press('Upload')
         ->see('Upload Successful!');
}

Making Custom HTTP Requests

If you would like to make an HTTP request into your application and get the full Illuminate\Http\Response object, you may use the call method from your tests:

public function testApplication()
{
	$response = $this->call('GET', '/');

	$this->assertEquals(200, $response->status());
}

If you are making POST, PUT, or PATCH requests you may pass an array of data with the request. Of course, this data will be available in your routes and controller via the Request instance.

$response = $this->call('POST', '/user', ['name' => 'Taylor']);

JSON API Testing

Laravel provides several helpers for testing JSON APIs and their responses. For example, the get, post, put, patch, and delete methods may be used to issue requests with various HTTP verbs. You may also easily pass data and headers to these methods, for example:

$this->post($uri, $data, $headers);

Verify JSON Contained In Response

To get started, let's write a test to make a POST request to /user and assert that a given array was returned in JSON format:

<?php

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
    	$this->post('/user', ['name' => 'Sally'])
    	     ->seeJson([
    	     	'name' => 'Sally',
    	     ]);
    }
}

The seeJson method converts the given array into JSON, and then verifies that the JSON fragment occurs anywhere within the entire JSON response returned by the application. So, if there are other properties in the JSON response, this test will still pass as long as the given fragment is present.

Verify Exact JSON Match

If you would like to check that the given array is an exact match for the JSON returned by the application, you should use the seeJsonEquals method:

<?php

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
    	$this->post('/user', ['name' => 'Sally'])
    	     ->seeJsonEquals([
    	     	'name' => 'Sally',
    	     ]);
    }
}

Sessions / Authentication

Laravel provides several helpers for working with the session during testing. First, you may set the session data to a given array using the withSession method. This is useful for loading the session with data before testing a request to your application:

<?php

class ExampleTest extends TestCase
{
    public function testApplication()
    {
		$this->withSession(['foo' => 'bar'])
		     ->visit('/');
    }
}

Of course, one common use of the session is for maintaining user state, such as the authenticated user. The actingAs helper method provides a simple way to force authenticate a given user as the current user:

<?php

class ExampleTest extends TestCase
{
    public function testApplication()
    {
    	$user = $this->factory->create('App\User');

		$this->actingAs($user)
			 ->withSession(['foo' => 'bar'])
		     ->visit('/')
		     ->see('Hello, '.$user->name);
    }
}

Working With Databases

Laravel also provides a variety of helpful tools to make it easier to test your database driven applications. First, you may use the seeInDatabase helper to assert that data exists in the database matching a given set of criteria. For example, if we would like to verify that there is a record in the users table with the email value of [email protected], we can do the following:

public function testDatabase()
{
	// Make call to application...

	$this->seeInDatabase('users', ['email' => '[email protected]']);
}

Of course, the seeInDatabase method and other helpers like it are for convenience. You are free to use any of PHPUnit's built-in assertion methods to supplement your tests.

Wrapping Tests In Transactions

If is often useful to wrap every test case in a database transactions so that the data generated by one test does not interfere with subsequent tests. Laravel provides a convenient DatabaseTransactions trait will automatically wrap each test in a transaction:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
	use DatabaseTransactions;

    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->visit('/')
             ->see('Laravel 5');
    }
}

Model Factories

When testing, it is common to need to insert a few records into your database before executing your test. Instead of manually specifying the value of each column when you create this test data, Laravel allows you to define a default set of attributes for each of your Eloquent models using "factories". To get started, take a look at the database/factories/ModelFactory.php file in your application. Out of the box, this file contains one factory definition:

$factory->define('App\User', function ($faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->email,
        'password' => str_random(10),
        'remember_token' => str_random(10),
    ];
});

Within the Closure, which serves as the factory definition, you may return the default test values of all attributes on the model. The Closure will receive an instance of the Faker PHP library, which allows you to conveniently generate various kinds of random data for testing.

Of course, you are free to add your own additional factories to the ModelFactory.php file.

Multiple Factory Types

Sometimes you may wish to have multiple factories for the same Eloquent model class. For example, perhaps you would like to have a factory for "Administrator" users in addition to normal users. You may define these factories using the defineAs method:

$factory->defineAs('App\User', 'admin', function ($faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->email,
        'password' => str_random(10),
        'remember_token' => str_random(10),
        'admin' => true,
    ];
});

Instead of duplicating all of the attributes from your base user factory, you may use the raw method to retrieve the base attributes. Once you have the attributes, simply supplement them with any additional values you require:

$factory->defineAs('App\User', 'admin', function ($faker) use ($factory) {
	$user = $factory->raw('App\User');

	$user['admin'] =  true;

	return $user;
});

Using Factories In Tests

Once you have defined your factories, you may use them in your tests or database seed files to generate model instances. Within your test classes, you have access to the $this->factory property. So, let's take a look at a few examples of creating models. First, we'll use the make method, which creates models but does not save them to the database:

public function testDatabase()
{
	$user = $this->factory->make('App\User');

	// Use model in tests...
}

Since the factory instance implements the PHP ArrayAccess interface, you may also make model instances by accessing the factory as an array:

$user = $this->factory['App\User'];

If you would like to override some of the default values of your models, you may pass an array of values to the make method. Only the specified values will be replaced while the rest of the values remain set to their default values as specified by the factory:

$user = $this->factory->make('App\User', [
	'name' => 'Abigail',
]);

You may also create a Collection of many models:

$users = $this->factory->of('App\User')->times(3)->make();

Persisting Factory Models

The create method not only creates the model instances, but also saves them to the database using Eloquent's save method:

public function testDatabase()
{
	$user = $this->factory->create('App\User');

	// Use model in tests...
}

Again, you may override attributes on the model by passing an array to the create method:

$user = $this->factory->create('App\User', [
	'name' => 'Abigail',
]);

Adding Relations To Models

You may even persist multiple models to the database. In this example, we'll even attach a relation to the created models. When using the create method to create multiple models, an Eloquent collection instance is returned, allowing you to use any of the convenient functions provided by the collection, such as each:

$users = $this->factory->of('App\User')
           ->times(3)
           ->create()
           ->each(function($u) {
				$u->posts()->save($this->factory['App\Post']);
			});

Working With Events

If you are making heavy use of Laravel's event system, you may wish to silence or mock certain events while testing. For example, if you are testing user registration, you probably do not want all of a UserRegistered event's handlers firing, since these may send "welcome" e-mails, etc.

Laravel provides a convenient expectsEvents method that will verify that the expected events are fired, but will prevent any handlers for those events from running:

<?php

class ExampleTest extends TestCase
{
    public function testUserRegistration()
    {
    	$this->expectsEvents('App\Events\UserRegistered');

    	// Test user registration code...
    }
}

If you would like to prevent any event handlers from running, you may use the withoutEvents method:

<?php

class ExampleTest extends TestCase
{
    public function testUserRegistration()
    {
    	$this->withoutEvents();

    	// Test user registration code...
    }
}

Working With Queues

Sometimes, you may wish to simply test that specific jobs are dispatched by your controllers when making requests to your application. This allows you to test your routes / controllers in isolation - set apart from your job's logic. Of course, you can test the job itself in a separate test class.

Laravel provides a convenient expectsJobs method that will verify that the expected jobs are dispatched, but the job itself will not be executed:

<?php

class ExampleTest extends TestCase
{
    public function testPurchasePodcast()
    {
    	$this->expectsJobs('App\Jobs\PurchasePodcast');

    	// Test purchase podcast code...
    }
}

Note: This method only detects jobs that are dispatched via the DispatchesCommands trait's dispatch methods. It does not detect jobs that are sent directly to Queue::push.

Mocking Facades

When testing, you may often want to mock a call to a Laravel facade. For example, consider the following controller action:

public function getIndex()
{
	return Cache::get('key');
}

We can mock the call to the Cache facade by using the shouldReceive method, which will return an instance of a Mockery mock. Since facades are actually resolved and managed by the Laravel service container, they have much more testability than a typical static class. For example, let's mock our call to the Cache facade:

<?php

class FooTest extends TestCase
{
	public function testGetIndex()
	{
		Cache::shouldReceive('get')
					->once()
					->with('key')
					->andReturn('value');

		$this->visit('/')->see('value');
	}
}

Note: You should not mock the Request facade. Instead, pass the input you desire into the HTTP helper methods such as call and post when running your test.