Tests with Phpunit in class that uses $_SERVER variable

Asked

Viewed 65 times

2

Hello, all right with you?

I created the following class for my own mini-framework (developed for study purposes).

<?php

declare(strict_types=1);

namespace Atlas\Core;

final class Request
{
    private $uri;

    public function __construct()
    {
        $this->uri = $_SERVER['REQUEST_URI'];
    }

    public function path(): string
    {
        return $this->uri;
    }
}

How can I test the method path of that class with Phpunit? I tried something like this

<?php

declare(strict_types=1);

namespace Atlas\Tests\Core;

use PHPUnit\Framework\TestCase;
use Atlas\Core\Request;

final class RequestTest extends TestCase
{
    private $request;

    public function assertPreConditions(): void
    {
        $this->request = new Request();
    }

    public function testCheckIfPathMethodReturnString(): void
    {
        $this->assertIsString($this->request->path());
    }
}

But it didn’t work. This class Request works perfectly when accessed by the browser. However, when I try to test it with Phpunit, this method returns null. Apparently $_SERVER values change since what Phpunit does is not a conventional web request.

Ideas?

Thank you very much.

  • 1

    Ideas? Yes. Remove your class dependency with global variables and you’ll be able to test more easily. Why not receive the URL as a parameter in the constructor? So, during the tests, you can set exactly the URL to be tested. In the production application it will be enough for you to pass the value of $_SERVER['REQUEST_URI'] as a parameter to function as expected.

  • @Woss Cara, doesn’t that make sense? I’ll wait to see if you have more equally useful answers to add something to the question. In the meantime, why don’t you post an answer with what you told me? Then, it’s likely I’ll dial!

1 answer

3


This is a clear sign that the construction of your class is mistaken. If it is difficult to test, then your class has a dependency built wrong. In this specific case, the dependency that is getting in the way is the global variable $_SERVER. As you noticed yourself, this will be a problem in unit tests precisely because this variable is usually automatically populated when receiving the HTTP request.

You can easily circumvent this limitation by injecting dependency, instead of the class Request access the URL part of the global variable it will receive this value per parameter.

classe Request
{
    private string $url;

    public function __construct(string $url)
    {
        $this->url = $url;
    }
}

In the application you create the instance with new Request($_SERVER['REQUEST_URI']), while during unit tests can instantiate with new Request('url/de/teste').

It is quite common, too, to see in the implementations that frameworks make a daughter class of Request defining the request from environment variables, for example:

classe ServerRequest extends Request
{
    public function __construct()
    {
        parent::__construct($_SERVER['REQUEST_URI'])
    }
}

Thus, you can apply all unit tests based on Request and in the application use ServerRequest, which will be covered by the tests precisely because of inheritance.

Thinking a DDD organization, you can consider the URL as a Value Object, then I could still declare the class Url and put it as a dependency on your class:

classe Request
{
    private Url $url;

    public function __construct(Url $url)
    {
        $this->url = $url;
    }
}

Browser other questions tagged

You are not signed in. Login or sign up in order to post.