GithubHelp home page GithubHelp logo

there4 / slim-unit-testing-example Goto Github PK

View Code? Open in Web Editor NEW
123.0 22.0 31.0 99 KB

Unit Testing Slim - Example PHPUnit route testing and mocking with the Slim Framework dependency injection container.

License: MIT License

PHP 89.16% HTML 10.29% ApacheConf 0.56%

slim-unit-testing-example's Introduction

Slim Unit Testing Example Build Status Code Climate

Integration and unit testing a Slim PHP application (Slim V2.x)

Slim V2.x

The current stable master of this project is for Slim V2.x. Work is in progress to bring this to the new Slim 3.

This is a sample application to show an approach to integration and unit testing a Slim application. To skip to the heart of this, go check out the testing bootstrap. It sets a mock environment and provides some helper methods for testing Slim routes.

About

Slim is a great PHP framework with a small footprint and everything you need to build fast applications. I've found it particularly well suited to delivering data to BackboneJS applications.

However, I haven't found a great deal of information about integration and unit testing with Slim, and have developed my own approach. I've refactored and introduced it into this sample application. I hope it will help others on their path to using this great framework.

This application demonstrates some techniques for integration and unit testing. With this approach, you'll be able to test your application without the need of Curl, webservers, or anything other than PHPUnit installed on your system. This makes it easy to test your entire app in an automated way with TravisCI. Check out the .travis.yml file in this project for an example.

Example

Here's a test for a very simple endpoint that returns the version from the application config. We're asserting that Slim responded with a 200 and that the version matches what we expect.

class VersionTest extends LocalWebTestCase {
    public function testVersion() {
        $this->client->get('/version');
        $this->assertEquals(200, $this->client->response->status());
        $this->assertEquals($this->app->config('version'), $this->client->response->body());
    }
}

Installation

Clone the repository and then run composer install and then phpunit. This application assumes that you have phpunit installed globally on your system. This application can be run as a functioning website. You can you use the sample apache config file in the build/ folder, or use the native php webserver. To use the php webserver, run php -S localhost:8080 -t public/ from the project root and open your browser to http://localhost:8080

Concepts

The public/index.php file serves as the application entry point. This file initializes a Slim $app with production configuration, includes the routes file from app/app.php and then runs the app with $app->run();. This allows us to keep our application separate from the index, and gives us an opportunity to include our app/app.php file in a different context.

When phpunit runs, it looks for the phpunit.xml file in our root. This file specifies a testing bootstrap file. PHPUnit includes testing/bootstrap.php. This file creates an $app, just like in build/index.php, but it uses testing configuration. The bootstrap keeps a reference to $app for the testing framework, and then provides several helper methods for GET, POST, PUT, PATCH, HEAD, and DELETE.

With these methods, you can run tests on Slim routes without a webserver. The tests run entirely within a mock environment and will be fast and efficient.

Unit Testing vs. Integration Testing

Unit tests should test an individual part of code. The system under test should be as small as possible. You would unit test an individual method. Integration testing exercises an entire system. Most of this example is about integration testing. We are running tests that work Slim from initial instantiation to the final delivery of data. With integration tests, we're treating the entire application as a unit, setting up a particular initial environment and then executing the run() command and finally inspecting the results to ensure that they match our expectations.

Mocking with Slim

See the ZenTest for an example of mocking with Slim dependency injection. In this test we mock a Curl wrapper class from Shuber. This allows us to substitute responses and exercise the parts of our application that we feel need testing. It also allows us to run these unit tests on systems that don't have the curl extension installed. We're totally isolated from that dependency while this running test.

The FileStoreTest uses a mock for the authentication class. Notice that the file store route doesn't use that class directly, but instead it is used by the application authenticator method. We're using the app dependency injection container to swap out the real object for a mock version. This approach allows us to control authentication results from within our test harness.

You can read more about dependency injection in the SlimDocs on DI, and more about mock objects in the PHPUnit docs.

Site Tooling

I'd like to give a nod to Pake. It's a flexible and powerful build tool written in PHP. If you've got lots of JavaScript, I might recommend Grunt or Gulp. However, for APIs and other sites that need a build system - I highly recommend Pake. It's got enough tools to handle SSH deployments and other sophisticated build steps. In this project, it's used to setup the dev web server and handle some code sniffs. With the Pake CLI tool you don't have to install it globally. I think it's a compelling and overlooked tool. Go see it!

Contributing

Open an issue for questions, comments, or suggestions. Pull requests are welcome, please format the code to PSR-2 standards and include an explanation of the benefits.

Contributors

Author Commits
Craig Davis 63
Jeremy Kendall 3
guillermo-fisher 1

Changelog

  • 0.1.1 Update Readme and remove echo and include in place of a proper rendering.
  • 0.1.0 Backwards compatibility breaking - Reorder the parameters on the get(), post() and http testing methods to be in the new order of $this->$method($path, $formVars, $optionalHeaders);. This makes the testing a little more terse, and clears up any confusion with improved parameter names.
  • 0.0.9 Bug fix for issue 4, with thanks to origal for his work in solving a problem with get parameters.

Thanks

Thanks must be given to Nicholas Humfrey for his work in this integration testing harness.

slim-unit-testing-example's People

Contributors

craig-davis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

slim-unit-testing-example's Issues

Question: Simple test goes wrong.

Hi!

Consider the following code please:
public/index.php:

<?php
//tiny example:

require_once '../vendor/autoload.php';

session_start(); //starts a session so we could use $_SESSION
$app = new \Slim\Slim();

require_once '../app/app.php';
$app->run();

app/app.php:

<?php
$app->get("/weather", function() use ($app){
    header('Content-type: application/json');
    $myArray = array("weather outside"=>"cool and smooth");
    //echo(json_encode($myArray));
    exit(json_encode($myArray));
});

tests/bootstrap.php is taken from here: https://github.com/there4/slim-unit-testing-example/blob/master/tests/bootstrap.php and like so, phpunit.xml is taken from this repo unchanged except for minor colors="true" addition.

tests/WeatherTest.php:

<?php

class WeatherTest extends Slim_Framework_TestCase
{
    /**
     * @runInSeparateProcess
     */
    public function testWeather() //obvious test
    {
        $this->get('/weather');
        $this->assertEquals(200, $this->response->status());
        var_dump($this->response->body());
        $this->assertEquals(json_encode('hellojjj'), $this->response->body());
    }
}

Findings:

  • I'm using phpunit 3.6.10. Was this approach tested on 3.6?
  • Opening /weather with the use of exit on app/app.php returns the desired 'application/json' response type. Commenting the exit from the route makes the response type text/html, albeit the header change on the route.
  • If I do not use the * @runInSeparateProcess annotation I get this error, which according to stackoverflow is known issue with phpunit:

exception 'ErrorException' with message 'Cannot modify header information - headers already sent by (output started at /usr/share/php/PHPUnit/Util/Printer.php:173)' in /home/oris/php-example/app/app.php:10

If I do use it, with the exit in the route, I get:
Configuration read from /home/oris/php-example/phpunit.xml

E

Time: 0 seconds, Memory: 2.75Mb

There was 1 error:

  1. WeatherTest::testWeather
    RuntimeException: {"weather outside":"cool and smooth"}

What am I missing? any recommendations (in addition to updating phpunit) would be greatly appreciated. Thanks.

Multiple request in one test method.

When I try to run multiple request in one test method, for example:

    public function testRoutesIdCondition()
    {
        $this->get('/user/1');
        $this->assertEquals(200, $this->response->status());

        $this->get('/user/1a');
        $this->assertEquals(404, $this->response->status());

        $this->get('/category/1a');
        $this->assertEquals(404, $this->response->status());
    }

The following requests are unable to get the correct response. (i.e., the remains requests will get a 200 instead of 404)

Maybe re-setup the mocking app before peforming every request will fix it. But I don't know if it's the best approach.

Get parameters don't get through from the test?

Hi,

Consider this route:

$app->get('/say-hello',function() use ($app){
   $name = $app->request->params()['name'];
   if (is_null($name)){
       echo "Hello John Doe!";
   } else {
       echo "Hello ".$name."!";
   }

Trying http://slim-api.com/say-hello from a browser echos Hello John Doe!
Trying http://slim-api.com/say-hello?name=Craig echos Hello Craig! respectively.

So, here's the test for it:

public function testSayHello()
    {
        $parameters = array('name'=>'Craig Davis');
        $this->get('/say-hello',$parameters);
        $this->assertEquals(200, $this->response->status());
        $this->assertSame("Hello Craig Davis!",$this->response->body());
    }

This test fails:

oris@oris:~/slim-api$ phpunit tests/MyTest.php 
PHPUnit 3.7.29 by Sebastian Bergmann.

Configuration read from /home/oris/slim-api/phpunit.xml

F

Time: 31 ms, Memory: 5.00Mb

There was 1 failure:

1) MyTest::testSayHello
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-Hello Craig Davis!
+Hello John Doe!

/home/oris/slim-api/tests/MyTest.php:31
/usr/share/php/PHPUnit/TextUI/Command.php:192
/usr/share/php/PHPUnit/TextUI/Command.php:130

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

Also, consider my addition to your tests/bootstrap.php updating the request globals. (As written in issue #3 ) Maybe we need to improve it somehow?

Not able to access headers within the app when testing

Hi @craig-davis, I'm not able to access the Referer header within the app when testing:

Sample code:


$app->get('/issue', function () use ($app) {
    $referer = $app->request->headers('referer');
    $response = $referer ? $referer : 'Missing referer header';
    $app->response->write($response);
});

Sample test:

public function testWithRefererWithPii()
    {
        $qs = array('');
        $referer = array('Referer' => 'hello');
        $this->client->get('/issue', $qs, $referer);
        $this->assertEquals('hello', $this->client->response->body());
    }

The phpunit results are:

There was 1 failure:

1) FilterPiiTest::testWithRefererWithPii
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'hello'
+'Missing referer header'

/vagrant/tests/FilterPiiTest.php:48
phar:///usr/local/bin/phpunit/phpunit/TextUI/Command.php:186
phar:///usr/local/bin/phpunit/phpunit/TextUI/Command.php:138

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

Any ideas?

Slim::getInstance

Firstly, thanks for the excellent guidance! I have been struggling with Slim integration testing for some time and this proved very useful indeed.

I did come across something that kept me occupied for a few hours - not a bug, but maybe worth mentioning.

I use Slim::getInstance() in my controllers, because I use the Class:method route syntax to keep things tidy. I know Slim can be injected in Slim3 using the home container, but I don't think so in Slim2.

When running a test class through phpunit, the first test succeeds but subsequent ones fail for no obvious reason. It turns out that Slim::getInstance() will always return the instance from the first test, unless passed a name.

bootstrap:

public function getSlimInstance()
{
    $app = new Slim(array(
          'mode' => 'testing'
     ));
     $app->setName('test');

     return $app;
}

The first time new Slim() is called the instance is stored in a protected static array inside Slim against the key "default". setName then adds another reference in the same array under the key "test".

Calling Slim::getInstance() returns the object from default which will always be the first one.

I solved it in a horrible way - tracked the application name in a static variable, so I can call Slim::getInstance(NAME)

Anyway - thought this might be useful to know!

Question: Passing PUT variables with a test

Hi!

Consider the following route:

$app->put('/users/me',function() use ($app){
    $app->response->headers->set('Content-Type', 'application/json');
    $userData = $app->request()->put();
    var_dump($userData);
});

This route basically accepts a PUT request and returns whatever variables came with it. When I test it with curl:

oris@oris:~/slim-api$ curl -i -H "Accept: application/json" -X PUT -d "hello=there" http://slim-api.com/users/me
HTTP/1.1 200 OK
Server: nginx/1.2.1
Date: Thu, 06 Feb 2014 09:53:11 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.4.6-1ubuntu1.5
Set-Cookie: PHPSESSID=2r6ta2fg6vdk7; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache

array(1) {
  ["hello"]=>
  string(5) "there"
}

This test:

public function testPutUsersMe()
    {
        $this->put('/users/me',"myKey=myValue");
        $userResponse = $this->response->body();
        var_dump($userResponse);
    }

Causes this error:

oris@oris:~/slim-api$ phpunit
PHPUnit 3.7.29 by Sebastian Bergmann.

Configuration read from /home/oris/slim-api/phpunit.xml

..........E

Time: 119 ms, Memory: 7.75Mb

There was 1 error:

1) UserTest::testPutUsersMe
auto loader error when loading PHPUnit_Extensions_Database_TestCase on auto loader error when loading PHPUnit_Extensions_SeleniumTestCase on Illegal string offset 'slim.input'

/home/oris/slim-api/tests/bootstrap.php:96
/home/oris/slim-api/tests/UserTest.php:140
/usr/share/php/PHPUnit/TextUI/Command.php:192
/usr/share/php/PHPUnit/TextUI/Command.php:130

FAILURES!
Tests: 11, Assertions: 85, Errors: 1.

And this test:

 public function testPutUsersMe()
    {
        $this->put('/users/me',array("myKey=myValue"));
        $userResponse = $this->response->body();
        var_dump($userResponse);
    }

Causes this response which isn't quite right:

oris@oris:~/slim-api$ phpunit
PHPUnit 3.7.29 by Sebastian Bergmann.

Configuration read from /home/oris/slim-api/phpunit.xml

...........string(13) "array(0) {
}
"


Time: 120 ms, Memory: 7.75Mb

OK (11 tests, 85 assertions)

Where is the array content? Am I missing something obvious?
Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.