# Testing
Tests run under PHPUnit, from the folder of the project or the library they belong to. **Done means `phpunit`
passes.**
```sh
cd libs/weather && phpunit # a library's own suite
phpunit # an application's, from its root
php tools/mmo.php verify --lib=weather # from the hub: lint + phpstan + tests on what changed
```
## `phpunit.xml`
At the root, beside `mmo.json` (which names it under its `tests` key).
```xml
tests/unitsrc
```
Two things there are easy to get wrong:
- **the suite points at `tests/unit`, not at `tests/`.** The house convention names a test class `TestThing`,
not `ThingTest`, so the suite cannot filter on a `Test` suffix — it matches `*.php` instead. Pointed at
`tests/` itself, it would collect `tests/bootstrap.php` and every helper beside it as a test.
- **`beStrictAboutOutputDuringTests`** is on, so anything printed during a test fails it. @see below.
## `tests/bootstrap.php`
It boots the application for the suite: the mode, the framework's own `TestCase`, and somewhere for the log to
go.
```php fragment
namespace AcmePortal\tests;
define('MMO_MODE', 'dev');
define('MMO_DEBUG', 'error+');
//in dev mode the framework also mirrors warnings to STDERR, which phpunit attributes to nothing in particular
define('MMO_DEBUG_STDERR', 0);
require(dirname(__DIR__) . '/bootstrap.php');
//the base class of the framework's own tests
require(dirname(__DIR__) . '/libs/core/tests/TestCase.php');
//phpunit is strict about output during tests, and the logger writes to stdout by default. NOT to null, which
//makes the Application hold every log until shutdown and then print the lot.
\MMO\app()->setStream(sys_get_temp_dir() . '/acme.portal-tests.log');
```
The `setStream()` line is the one people miss: without it the logger writes to stdout and
`beStrictAboutOutputDuringTests` fails tests that were otherwise fine. Sending it to null is worse — the
Application then holds every log until shutdown and prints the lot at the end.
## A test
`tests/unit/TestArticle.php` — named `TestArticle`, not `ArticleTest`:
```php fragment
namespace AcmePortal\tests\unit;
use AcmePortal\tests\TestCase;
class TestArticle extends TestCase {
public function testItValidatesItsName(): void {
$article = new \AcmePortal\entities\Article(['name' => '']);
$this->assertFalse($article->isValid());
}
}
```
Most of the framework needs no server and no database to exercise: `require 'bootstrap.php';` and use the class
directly.
`php tools/mmo.php make test --lib=` writes a first test for an existing class, and `make entity`
and `make controller` each write one along with the class. @see NewLibrary.md
## Test a page or a route, as a browser asks for it
`MMO\core\tests\TestCase::http()` hands the application a request in the test's own process: the same routing,
controllers, pages and templates as the web server, with no server -- and as many requests in a row as the test
needs. A page, a form posted to it, an api route, a download:
```php fragment
class TestInvoices extends \MMO\core\tests\TestCase {
public function testTheFormRefusesAnEmptyCustomer(): void {
$this->assertSame(200, $this->http('GET', 'invoices')->getStatusCode());
$refused = $this->http('POST', 'invoices/new', ['customer' => '']);
$this->assertSame(422, $refused->getStatusCode());
$this->assertStringContainsString('customer', (string)$refused->getBody());
$this->assertSame('application/pdf', $this->http('GET', 'invoices/1.pdf')->getHeaderLine('Content-Type'));
}
}
```
On the command line the site's url is a file's, which no route matches: `http()` makes it `http://localhost`
for the request. A page needs mmo.client's Theme; `http()` builds it when mmo.client is loaded.
To look at the site itself, serve it: `php -S localhost:8000 -t www www/index.php` (a project's `www/index.php`
lets PHP's built-in server deliver the files that exist), or apache.