How to Test PHP/Laravel BackEnd

Raissa Correia
4 min readJun 4, 2019

--

Just a Random Illustration. Photo by Caspar Camille Rubin on Unsplash

This is a continuation to my last text, about testing. Our system has a React Front and a PHP Laravel Back with a SQL database. Now I’ll talk about the back-end tests. This will be shorter because the Laravel documentation is pretty good, and this kind of tests are more than a decade old.

Configuration

You can read a lot more details in: https://laravel.com/docs/5.8/testing, Laravel has a really good documentation, and I recommend it.

We’ll focus in a few files, essential to make your development environment possible.

buildMyTestDb.sh

I used “mysqltest” as the test DB name, and I did everything in Fedora 29. Always good to interrupt and delete the old DB to make sure everything is clear. That’s what we are doing now.

docker-compose stop mysqltest
sudo rm -Rf ~/.laradock/data/mysqltest

Initialize the nginx is necessary and you can also run the build line without “-d” to see the building process of MySQL that make a lot of optimizations and the DB will not be ready when it says so. Wait a couple minutes!

docker-compose up — force-recreate — build -d mysqltest
docker-compose up -d nginx

After this couple minutes initialize the php workspace to run your tests with phpunit command followed by your folder or file path.

docker-compose exec workspace bash

phpunit.xml

Between PHP tags. Creating your own MySQL DB for test is always recommended. Remember to change it when you’ll need to work with the dev or production DB.

<env name=”DB_HOST” value=”mysqltest”/>

.env

The.env change makes practical to the creation and maintaining the test DB because they use the same ip and port, and you don’t need to take care of the port management.

#DB_CONNECTION=mysql
#DB_HOST=mysql
DB_HOST=mysqltest
DB_HOST=#IP
DB_PORT=3306 #the standard port

docker-compose.yml (in laradock folder)

The “.yml” makes the docker recognize it as any other MySQL DB, because the args, the environments are the same and the volumes are analogous.

### MySQLTest ################################################
mysqltest:
build:
context: ./mysql
args:
- MYSQL_VERSION=${MYSQL_VERSION}
environment:
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- TZ=${WORKSPACE_TIMEZONE}
volumes:
- ${DATA_PATH_HOST}/mysqltest:/var/lib/mysql
- ${MYSQL_ENTRYPOINT_INITDB}:/docker-entrypoint-initdb.d
networks:
- backend

Unit Tests

Models and Validate the Relationships

The unit tests are one for each model, the purpose is to test its methods and its relationships. An easy and basic example is this, in which we test if the owner of “myModel” is an object type User.

public function testOwner()
{
$model = myModel::first();
$this->assertInstanceOf(User::class, $model->owner);
}

Sometimes it demands an data field especially in the creation methods:

public function testCreateComment()
{
$answer = new Comment();
$data = [
‘commentary’ => “content of this comment”,
‘post_id’ => 1,
‘comment_id’ => “2”
];
$this->assertInstanceOf(Comment::class, $answer>create($data));
}

An method test is something like this: That makes sure that totalLikes() method returns an integer.

public function testTotalLikes()
{
$model = myModel::first();
$this->assertInternalType(“int”, $model->totalLikes());
}

Feature Tests

Users

The most important thing to understand is the fact that we are running PHP artisan tinker commands.

So to get the user(or users) variables from DB to our code we use the same line of code.

$user = User::find(id);

Or use a more generic to don’t depend from hard-coded users, I suggest to search for the first of type “example”.

Uri, Data e Params

The next sections is completely based n the API specification.

You’ll need a variable which I prefer to call uri that will contain the URL section necessary with the HTTP method to run an specific endpoint.

Also some endpoints that require queries like the search ones demands a “URI Params” and the $data field to complete the request body.

Response, Assertions(type, status, message)

After the request body preparation, the response will be generated calling it thought the HTTP method, and the response will be a JSON that we need to make the assertions.

I divide the assertions in 3 types: By Status, by content and by type.

  • Status: These are the basic HTTP Status Code. You can check all them here:

https://www.restapitutorial.com/httpstatuscodes.html

And the assertion has this syntax:

$response->assertStatus(200);
  • Type:
parent::assertPaginatorJsonStructure($response);
$myThing_id = $response->json()[‘message’];
$this->assertInternalType(‘int’, $myThing_id);
  • Other Assertions you can checkout in the Laravel docs, that are great

https://laravel.com/docs/5.8/http-tests#available-assertions

Final Structure

/**
* Describing Endpoint
* @depends Endpoint_Dependency
* @return void
*/
public function testMyEndpoint($param)
{
[$dependency_one,$dependency_two] = $param;
$user = User::find(id);
$uri = ‘/’ . $dependency_one . ‘/something/’ . $dependency_two . ‘/anotherthing/’ . $stuff_id;
$data = [php_array_thatll_be_converted_to_json_later];
$response = $this->http_method($uri, $data, parent::getApiAuthHeaders($user));
$response->assertStatus(200);
$response->AnyAssertionYouWish;
}

Dependencies and Simulating Workflow

Some endpoint depend in a DB change cause by another endpoint, so to simulate a workflow we include in our code the following:

In the last line of the parent endpoint:

return [$first_dependency,$second_dependency];

In the beginning of the son endpoint:

The between /** */ part also documents this test

/**
* Endpoint Description
* @depends testDependency
* @return void
*/
public function testEndpointWithDependency($params)
{
[$first_dependency,$second_dependency] = $params;
…etc…

Considerations

Huge Thanks to Clickideia, Educational Company which this tests belongs to.

Any questions? Comment! I’m not experienced with technical texts in Medium, so it can have some problems.

--

--

Raissa Correia

Just a brazillian fullstack dev @raideveloper on twitter and instagram