Qafoo GmbH - passion for software quality ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Author: Manuel Pichler :Date: Thu, 09 Dec 2010 09:37:11 +0100 :Revision: 20 :Copyright: All rights reserved ============================= Testing file uploads with PHP ============================= :Keywords: php, test, file, upload, phpt, phpunit, automated testing :Description: An extensive tutorial on how to test file uploads in PHP using PHPT and PHPUnit. :Abstract: A question I am asked on a regular basis is, how you can test a file upload with PHP. In this blog post, I take a precise look at this topic and show you how to test your file uploads from within your standard testing environment, using widely unknown testing framework for PHP. A question I am asked on a regular basis is, how you can test a file upload with PHP. In this blog post, I take a precise look at this topic and show you how to test your file uploads from within your standard testing environment, using widely unknown testing framework for PHP. Let me begin with the testing framework that we all know and use in our daily work, `PHPUnit`__. With this testing framework you have the opportunity to test almost every part of your software. This also applies to file uploads, as long as the application under test realizes uploads only via the PHP's magic ``$_FILES`` variable. In this case you can easily prepare an appropriately crafted $_FILES array in the test's ``setUp()`` method, that can be accessed by the software under test: __ http://phpunit.de .. note:: Get `20% discount`__ on PHP OOP/OOD trainings in January! __ /blog/012_20_discount_oop_ood_training_january_2011.html :: // ... protected function setUp() { parent::setUp(); $_FILES = array( 'file_valid' => array( 'name' => 'foo.txt', 'tmp_name' => '/tmp/php42up23', 'type' => 'text/plain', 'size' => 42, 'error' => 0 ) ); } // ... But fortunately, in most cases this is not quite so simple because the software to be tested utilizes the safer PHP functions ``is_uploaded_file()`` and ``move_uploaded_file()``. And in this case the manipulation of the ``$_FILES`` array does not work, because both functions operate on another level, so that you cannot manipulate the input data within userland code:: class UploadExample { protected $dest; public function __construct($dest) { $this->dest = rtrim($dest, '/') . '/'; } public function handle($name) { if (is_uploaded_file($_FILES[$name]['tmp_name'])) { move_uploaded_file( $_FILES[$name]['tmp_name'], $this->dest . $_FILES[$name]['name'] ); } } } .. note:: Need a `PHP software quality expert`__? Qafoo provides consulting and training in this area! __ /services/consulting.html Of course, you can still test this part of the application with a heavyweight framework like `Selenium`__. Which, however, brings a number of disadvantages: You must prepare and integrate a variety of other infrastructure components. Beginning with a webserver, the Selenium server, a graphical user interface with a browser and other infrastructure + bootstrap code that is required for a working version of the software. All this increases the required effort to execute a single test and the execution time of the test itself. This carries the danger that false positives arise, caused as a side effects from the complex test infrastructure. __ http://seleniumhq.org An alternative is the *PHP Testing Framework* or in short *PHPT*, which the core developers use to test `PHP`__ itself and that is used by various other PHP related projects like `PEAR`__ and `PECL`__. I would describe PHPT as a lightweight testing framework, that is easy to learn due to its simple syntax and a very expressive description format. In this article I will only take a look at a limited set of the PHPT syntax. A complete documentation of PHPT can be found on the `PHP Quality Assurance`__ website. The following example shows the three base elements of a minimal PHPT test case. __ http://php.net __ http://pear.php.net __ http://pecl.php.net __ http://qa.php.net/phpt_details.php # A description of the test itself:: --TEST-- Example test case # The code under test(CUT):: --FILE-- handle('file'); var_dump(file_exists('/tmp/example.rst')); ?> --EXPECT-- bool(true) And that's it. You have a complete PHPT test. Of course, you verify that it works by calling the ``run-tests`` command:: ~ $ pear run-tests upload-example.phpt Running 1 tests PASS Example test emulating a file upload[upload-example.phpt] TOTAL TIME: 00:00 1 PASSED TESTS 0 SKIPPED TESTS The test runs and the task has been completed successfully. Maybe you wonder, how you can integrate these PHPT tests into an existing test infrastructure? A fairly simple solutionexists: A relatively unknown feature of `PHPUnit`__ is the built-in support for PHPT tests. This provides the great benefit that you must not worry about the integration, your tests must only inherit from the appropriate test classes: __ http://phpunit.de - PHPUnit_Extensions_PhptTestCase - PHPUnit_Extensions_PhptTestSuite The ``PHPUnit_Extensions_PhptTestCase`` class can be used to reference a single PHPT test file, which is then executed by the PHPUnit testing framework. It has to be noted that the absolute path to the PHPT file must be specified:: assertEquals(2, \MyClass::add(1,1)); } public function testUploadedFile(){ $this->assertTrue(is_uploaded_file('foo')); } } } Tyrael - Sebastian Bergmann at Thu, 09 Dec 2010 22:56:31 +0100 Instead of Runkit you may want to have a look at https://github.com/sebastianbergmann/php-test-helpers - Jean-Marc Fontaine at Thu, 09 Dec 2010 23:26:41 +0100 Internet is great! Yesterday, I was asking myself how to unit test file uploads and the next day you publish this article. :) Thanks! - Tyrael at Fri, 10 Dec 2010 14:07:32 +0100 Thanks Sebastian, I missed the rename_function method in your extension. Tyrael - Ralf at Tue, 14 Dec 2010 10:51:35 +0100 Hello, using extensions to redefine function names is considered as anti pattern "The Local Hero" here: http://blog.james-carr.org/2006/11/03/tdd-anti-patterns/ Would'nt it be better to wrap an API around these build-in PHP functions? Ralf - Tyrael at Tue, 14 Dec 2010 16:12:47 +0100 I can't see the renaming to be explicitly mentioned here. you could configure your test cases to check for the required dependency and skip them, if isn't available. of course you can put in some effort and refactor the code to be more easy to test, hence you can spare yourself from the above mentioned techniques. Tyrael - Thomas H at Wed, 15 Dec 2010 11:33:16 +0100 Hello, I need to test an upload file and I am really interested in your solution. Unluckily I have problems with it. I'm working under zend framework and test with PHPUnit. I tried the simple example and it works prefectly. My problems begin with the file. I track package with Wireshark and get : ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0 Content-Disposition: form-data; name="Filename" example-text.phtml ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0 Content-Disposition: form-data; name="folder" /admin/configuration/ ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0 Content-Disposition: form-data; name="Filedata"; filename="example-text.phtml" Content-Type: application/octet-stream Ceci est un example ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0 Content-Disposition: form-data; name="Upload" Submit Query ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0-- My test is a part of TestSuite but it is not a problem cause I declare directly the PHPT object in it. Here is my test : public function testUploadLayout() { $phpt = new PHPUnit_Extensions_PhptTestCase( __DIR__.'/upload-layout.phpt', array('cgi' => 'php-cgi') ); $result = $phpt->run(); $this->assertTrue($result->wasSuccessful()); } This work with the simple example but not with upload. $result->errors() returns me an empty array and $result->failures returns something which hasn't end with the tracking of all my zend structure. I don't understand anything. My phpt file : --TEST-- Example test emulating a file upload --POST_RAW-- ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0 Content-Disposition: form-data; name="Filename" example-text.phtml ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0 Content-Disposition: form-data; name="folder" /admin/configuration/ ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0 Content-Disposition: form-data; name="Filedata"; filename="example-text.phtml" Content-Type: application/octet-stream Ceci est un example ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0 Content-Disposition: form-data; name="Upload" Submit Query ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0-- --FILE-- --EXPECT-- test It doesn't work either with a copy paste of your example. I will be very grateful if you find anything to help me. Thank you. Thomas - Matt Schuckmann at Fri, 02 Mar 2012 23:51:18 +0100 I can't get the upload_example.phpt test to work at all. For one in order for the --POST_RAW-- to even have a chance at working in the plain old pear run-tests environment you must included the --cgi=PHPCGI option like so pear run-tests --cgi=PHPCGI upload-example.phpt However, even with this option the test always. Furthermore when I try run it through phpunit cli I must explicitly tell it to run the Alltests.php script or else it confused by UploadExampleTest (UploadExampleTest.php) which does not derive from PHPUnit_Framework_TestCase. And even if I run Alltests.php the tests are just skipped, presumably because the cgi environment is not setup. There really isn't much info out there on this stuff, has anyone gotten this to work? - Glenn at Sun, 09 Feb 2014 21:23:25 +0100 After searching for an answer to how to test file uploads in zend framework using phpunit, I eventually found this article and thought, 'Great, finally, I've found the answer!'. Well, I've got as far as downloading Wireshark and now I'm stuck. From what I can see, there's nothing in the Wireshark manual to tell me how to capture --POST_RAW-- data. And nothing from my initial playing with the software seems to produce anything remotely resembling the data you have above. As this is a key feature of this article, some pointers would be appreciated! :) - Alex at Mon, 29 Sep 2014 05:43:14 +0200 You can capture the POST message using web-browser with Chromium Developer Tools - go to the Network section where request to the submitted page will be listed. Click the request and the complete message will be listed there. - Rasmus Schultz at Wed, 03 May 2017 08:11:11 +0200 Unless you're writing your own implementation of PSR-7, you shouldn't need this. Use PSR-7 and all of this is neatly testable - someone else has done the hard work of testing their PSR-7 implementation so that you will never need to worry or struggle with these kinds of tests again :-) - Oliver Russell at Mon, 12 Mar 2018 14:24:09 +0100 While adding PHP file upload function (https://www.cloudways.com/blog/the-basics-of-file-upload-in-php/ ) to your website, it is also necessary to validate the file type and other things. Also, check for any sql injection vulnerabilities. Forms like these tend to have such vulnerabilities.