My blog, keeping you up-to-date on my latest news.

 

Testing upload file with php

PHP • 2012-12-29

 

A few days ago I've been wondering how to test file upload with PHPUnit.
PHPUnit is the de-facto standard for unit testing in PHP projects. It provides both a framework that makes the writing of tests easy as well as the functionality to easily run the tests and analyse their results.
But some fonctions are safe and can't be executed in other scope than HTTP POST like is_uploaded_file and move_uploaded_file.
You will find in this article some useful tips allowing you to test reliability of you applicaton, concerning file upload.

Let's start with simple class for file upload, which provides two main features:

  1. A constructor to define the destination directory
  2. A receive method in which we move the uploaded file to its final destination.
<?php
namespace Library;

class Upload
{
    /**
     * Destination directory
     *
     * @var string
     */
    protected $_destination;

    /**
     * Constructor
     *
     * @param string $destination
     */
    public function __construct($destination)
    {
        $this->_destination rtrim($destination'/');
    }

    /**
     * Receive file
     *
     * @param string $name
     * @return boolean
     */
    public function receive($name)
    {
        if(empty($_FILES[$name]) or !is_uploaded_file($_FILES[$name]['tmp_name']))
        {
            return FALSE;
        }

        return move_uploaded_file($_FILES[$name]['tmp_name'], $this->_destination '/' $_FILES[$name]['name']);
    }
}

Next, we need to test our class using PHPUnit. In order to do that, you can use the following command to generate the test class skeleton.

phpunit-skelgen --test Upload

After a few modifications, you'll get something like this:

<?php
namespace Library;

/**
 * @backupGlobals disabled
 * @backupStaticAttributes disabled
 */
class UploadTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var Upload
     */
    protected $_object;

    /**
     * Sets up the fixture, for example, opens a network connection.
     * This method is called before a test is executed.
     */
    protected function setUp()
    {
        $_FILES = array(
            'test' => array(
                'name' => 'test.jpg',
                'type' => 'image/jpeg',
                'size' => 542,
                'tmp_name' => __DIR__ '/_files/source-test.jpg',
                'error' => 0
            )
        );

        $this->_object = new Upload(__DIR__ '/_files/');
    }

    /**
     * Tears down the fixture, for example, closes a network connection.
     * This method is called after a test is executed.
     */
    protected function tearDown()
    {
        unset($_FILES);
        unset($this->_object);
        @unlink(__DIR__ '/_files/test.jpg');
    }

    /**
     * @covers Upload::receive
     */
    public function testReceive()
    {
        $this->assertTrue($this->_object->receive('test'));
    }
}

During the first execution of the tests you should get something like this:

got@GoT:/var/www/GoT/blog/testing-upload-file-with-php/tests$ ./runtests.sh
+ phpunit --verbose
PHPUnit 3.7.10 by Sebastian Bergmann.

Configuration read from /var/www/GoT/blog/testing-upload-file-with-php/tests/phpunit.xml

F

Time: 0 seconds, Memory: 2.75Mb

There was 1 failure:

1) Library\UploadTest::testReceive
Failed asserting that false is true.

/var/www/GoT/blog/testing-upload-file-with-php/tests/Library/UploadTest.php:49

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

As you can see, the test failed. So, why did it fail?
First of all, most of PHP developers fortunately use safe functions to test if a file has been uploaded (is_uploaded_file) or to move an uploaded file (move_uploaded_file).
But sadly, in CLI (Command Line Interface) both these methods return FALSE.

If you didn't notice that already, we're using namespaces and thanks to it we can bypass this behaviour for tests because namespaces allow us to override native PHP functions.
For example we know that function is_uploaded_file tells wether the file was uploaded via HTTP POST, so we just have to override it.

<?php
function is_uploaded_file($filename)
{
    //Check only if file exists
    return file_exists($filename);
}

Our test will pass the first condition but will not be TRUE because move_uploaded_file checks to ensure that file is a valid uploaded file.
Ok, override it too.

<?php
function move_uploaded_file($filename$destination)
{
    //Copy file
    return copy($filename$destination);
}

Write this function in Bootstrap or test class, but never forget to write them in the same namespace than Upload class.

Execute test again and it's done!

got@GoT:/var/www/GoT/blog/testing-upload-file-with-php/tests$ ./runtests.sh
+ phpunit --verbose
PHPUnit 3.7.10 by Sebastian Bergmann.

Configuration read from /var/www/GoT/blog/testing-upload-file-with-php/tests/phpunit.xml

.

Time: 0 seconds, Memory: 2.75Mb

OK (1 test, 1 assertion)

You can download full example source on github

<< Back to Blog Discuss this post

 

Comments

 

No comments

 

Add a comment

 
  • Please verify you are human

Categories