API Endpoint testing with PHPUnit consistently returns 404's, what did I configure incorrectly?


#1

Hi, I’ve been struggling with this for over 2 days at this point and I think that asking for help is the best way to try solving my issue(s).

I can access all these pages from my browser and from Postman, as they are coming from an Ubuntu/Apache local server. My app.php returns an instance of $app and my index.php runs it, as most examples also do.

My PHPUnit is correctly configured (with phpunit.xml and tests/bootstrap.php), and is able to run the simple examples tutorials recommend for checking if it works. However, as soon as I try to use PHPUnit with my application, I end up with 404’s on every page. I might not be pointing at the right pages, but I’ve tested many different options with no luck.

I have exhausted my resources at this point, having searched and found many different examples (such as “Slim-Test-Helper”, “Slim-Integration-Testing-Example”, Akrabat’s various resources, and several other websites). What should I do?

My site uses the RKA\Session middleware to handle logging in, but I haven’t explicitly configured anything to interface with it in the tests, could that be part of the issue?

Thank you in advance to anyone that can help with this, I truly appreciate it.


PHPUnit returns the following message:

There was 1 failure:

1) Demo\Test\DemoTest::testCheckBase
Failed asserting that 404 matches expected 200.

/var/www/demo/api/mir/tests/BaseTestCase.php:102
/var/www/demo/api/mir/tests/integration/DemoTest.php:11

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

The content of DemoTest is as follows, where the entity path would at least return an authorization failure instead of a server failure if it worked:

<?php

namespace Demo\Test;

class DemoTest extends BaseTestCase
{
  public function testCheckBase()
  {
    $this->request('GET', '/entities');
    
    $this->assertResponseStatus(200);
  }
}

and the content of BaseTestCase is the following:

<?php

namespace Demo\Test;

use Slim\App;
use Slim\Http\Environment;
use Slim\Http\Headers;
use Slim\Http\Request;
use Slim\Http\RequestBody;
use Slim\Http\Response;
use Slim\Http\Uri;

abstract class BaseTestCase extends \PHPUnit\Framework\TestCase
{
  /** @var Response */
  private $response;

  /** @var \Slim\App */
  private $app;

  /** Sets the Slim app definition for each test */
  protected function setUp()
  {
    $this->app = require __DIR__.'/../public/app.php';
    // print_r($this->app);
    // die();
  }

  /** Destroys local content of global vars */
  protected function tearDown()
  {
    unset($this->app);
    unset($this->response);
  }

  /** Calls the slim app to handle the request
   * 
   * @param $method
   * @param $url
   * @param $reqParams
   * 
   * @return $reponse 
   */
  protected function request($method, $url, array $reqParams = [])
  {
    $request =  $this->prepRequest($method, $url, $reqParams);
    $response = new Response();

    $app = $this->app;
    $this->response = $app($request, $response);
    print_r($request);
    // die();
  }

  /** Returns JSON decoded response body as array */
  protected function responseData()
  {
    return json_decode((string) $this->response->getBody(), true);
  }

  /** Returns request object
   * 
   * @param $method
   * @param $url
   * @param $reqParams[]
   * 
   * @return $request
   */
  private function prepRequest($method, $url, array $reqParams)
  {
    $env = Environment::mock([
      'REQUEST_URI' => $url,
      'REQUEST_METHOD' => $method
    ]);

    $parts = explode('?', $url);

    if(isset($parts[1])) {
      $env['QUERY_STRING'] = $parts[1];
    }

    $uri = Uri::createFromEnvironment($env);
    $headers = Headers::createFromEnvironment($env);
    $cookies = [];

    $serverParams = $env->all();

    $body = new RequestBody();
    $body->write(json_encode($reqParams));

    $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body);
    return $request->withHeader('Content-Type', 'application/json');
  }

  /** -----------------------
   * Custom assertion helpers
   * ------------------------
   */

  protected function assertResponseStatus($expectedStatus)
  {
    $this->assertEquals($expectedStatus, $this->response->getStatusCode());
  }

  protected function assertResponseContentType($expectedContentType)
  {
    $this->assertContains($expectedContentType, $this->response->getHeader('Content-Type'));
  }

}

#2

Hello!

I was dealing with the same problem a few days ago. Finally, I managed to do full integration tests with Slim. I plan to write a blog post about this topic. Until then, all I can do is to show you my sample code and explain a few things.

First you have to prepare a few things:

phpunit and sessions are not the best friends. As soon as a session is started and data is written to the session, the 2nd unit test fails because it has filled the PHP output buffer. This problem can be solved by activating the output buffer in a bootstrap. php file specific to phpunit.
Example

The phpunit setUp() method must be used to reinitialize the complete slim app.

Example

A special environment and configuration should be created for the phpunit tests. Otherwise, unit tests may be executed in the development database.

integration.php, config.php,
phpunit.xml

Not all session libraries are suitable for unit tests. I tried slim session, Aura\Session, Zend\Session and other libraries, but without success. For this reason I have written my slim session library which provides a MemorySessionAdapter.

For each test you have to create a mocked request object and adapt it according to the individual test conditions.

The main problem was to run the middleware for each test. I could only achieve this by calling the $app->run() method. The other method like $response = $app($request, $response); unfortunately does not work in combination with middleware. Since it only worked with the run() method, I have to find a way to place the request object in the fixed container. I achieved this relatively easily with Reflection.

Here you can find the code for the ApiTestCase.php and some example tests.

Good luck :slight_smile:


#3

Hi Odan, unfortunately I wasn’t able to get it working even after following most* of your recommendations.

(* everything except changing the session manager)

I don’t have the option of changing the session manager being used, so my team ended up deciding to perform integration tests with Postman and stick to unit testing with PHPUnit (as it was intended).

Thanks for your help!


#4

I never had any difficulty with PHPunit. I have used it with slim and other PHP apps without any such issues. You should check your test case from the beginning. You should also check whether you have organized the directory structure correctly or not. Create a folder UnitTestFiles. In this folder, create a subfolder Test. Create a new file phpunit.xml in this subfolder and add the following code to it.

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" colors = "true" verbase="true" stopOnFailute="false">

<testsuites>
<testsuite name="Application Test Suite">
<directory>/UnitTestFiles/Test/</directory>
</testsuite>
</testsuites>
</phpunit>

The colors="true" will show the results in highlighted colors and < directory>./UnitTestFiles/Test/</directory> will ask PHPUnit for the location of the files to be tested.

Now the directory structure will look like:

|–vendor
|–UnitTestFiles/Test
|–phpunit.xml
|–composer.json
|–composer.lock
|–index.php