SDBlackwood
SDBlackwood

Reputation: 61

PHPUnit test fails because file path location is incorrect

I’m trying to write a simple unit test for a single function in my Slim PHP application. The function creates a directory using mkdir() and I want the test to assert that the directory is created successfully. When tested using the front end application, the folder is created successfully, however when running the test, the directory is not found

1) TestController::testCreate mkdir(): No such file or directory

We are autoloading classes using the follow:

//composer.json
    "autoload": {
        "psr-4": {
            "App\\": "app",
            "tests\\": "tests/"
        }
    }

This is the function that creates the directory:

//Controllor.php
public function create($request, $response){
   mkdir("../public/folder");
}

Using the application, this creates a directory in the following location as expected

-app
-public/folder

This is an example of the test function:

//TestController.php
public function testCreate(){

    $controller = new Controller($this->container);
    $request = $this->requestFactory();
    $response = new \Slim\Http\Response();

    $response = $controller>create($request, $response, []);
    //Assertions below..
}

I am using a phpunit.xml to bootstrap the autoload.php file by inserting bootstrap="vendor/autoload.php" into the config file.

I have also tried to require the vendor/autoload.php from the TestController.php and have tried to manually set $_SERVER['DOCUMENT_ROOT'] in TestController.php

How can I autoload the namespaces while running TestController.php so that the create() function is invoking mkdir() from the correct location?

Upvotes: 3

Views: 1311

Answers (2)

odan
odan

Reputation: 4952

You have multiple options

  1. With vfsStream it's possible to mock the filesystem. Works great with all PHP functions like mkdir, file_exists, etc...
  2. Use Flysystem as abstraction library for the filesystem. Use the Null Adapter for tests.
  3. Create a custom interface and implement a real filesystem class and a in-memory mock class for the tests.

Upvotes: 1

alx
alx

Reputation: 2357

Well, there is a small problem and a bigger problem.

Small one -- you're loosing context, specifically working directory. Thus, not clear where your directory is being created, and if that place is actually writable. To overcome this, I'd make a configurable value -- a directory name where things should happen. For real use case I'd supply $_SERVER['DOCUMENT_ROOT'] (or whatever other value is suitable), and for tests I'd use /tmp (or better sys_get_temp_dir()).

To better word first problem (one you're asking about):

  • currently your code assumes something about running environment, and that assumption is not a safe one like "there is always array_keys() function available"
  • it is a good practice to convert such assumptions into configurable values
  • making it configurable also makes it much easier to test, because your test environment is decoupled from your prod/dev/staging environments

Bigger one -- this is not a unit test, because you're checking how your code interacts with some external entity, in this case filesystem. This is a functional test. To make it unit test, you need to mock mkdir(), and to do that you need to abstract it away behind some interface like this:

interface DirMaker {
    public function mkdir($name);
}

Implementation of that interface could handle configuration too. And in test you could provide a mock that just acknowledges the fact that mkdir() was called, as expected.

Upvotes: 1

Related Questions