Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Zend Test Laravel Package

zendframework/zend-test

Zend Test provides PHPUnit integration and testing helpers for Zend Framework applications, including MVC and controller test cases, route assertions, DOM and response checks, redirects, headers, and common test utilities.

Deep Wiki
Context7

Getting Started

zendframework/zend-test is not a test framework — it is the PHPUnit glue that lets you boot a real Zend\Mvc\Application inside a test and dispatch() a URL through the router, dispatcher, view layer and response, without touching a web server. If you work on a ZF2/ZF3 MVC app, this is your fastest feedback loop for "does this route/controller/view actually work end-to-end."

Minimal setup:

composer require --dev zendframework/zend-test

Then point PHPUnit at a bootstrap that sets APPLICATION_ENV=testing and loads the Composer autoloader (modules are usually found via Composer autoload/include-path, or via Zend\Loader\StandardAutoloader if you use the classic module/ layout).

Your first test — the one you will write a hundred times:

namespace ApplicationTest\Controller;

use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;

class AuthControllerTest extends AbstractHttpControllerTestCase
{
    protected function setUp()          // ': void' on PHPUnit 8+
    {
        $this->setApplicationConfig(
            include __DIR__ . '/../../config/application.config.php'
        );
        parent::setUp();
    }

    public function testLoginRedirectsToDashboard()
    {
        $this->dispatch('/login', 'POST', ['username' => 'demo', 'password' => 'secret']);

        $this->assertResponseStatusCode(302);
        $this->assertRedirectTo('/dashboard');
        $this->assertMatchedRouteName('login');
    }
}

Where to look first, in this order:

  1. Zend\Test\PHPUnit\Controller\AbstractControllerTestCase — dispatch(), reset(), getApplication(), getApplicationServiceLocator(), getRequest(), getResponse(), getRouteMatch(), url(), plus the route/controller/action assertions.
  2. AbstractHttpControllerTestCase — adds the HTTP assertions: assertResponseStatusCode, assertRedirectTo, assertRedirectRegex, assertQuery*, assertXpath*, assertHeaderContains-style helpers.
  3. AbstractConsoleControllerTestCase — same idea for zend-console routes.
  4. Zend\Test\Util\ModuleLoader — the actual bootstrapper, usable standalone from a smoke script or a data-seeding command.

Implementation Patterns

One base test case per test suite. Subclass AbstractHttpControllerTestCase once, put the config include, common service overrides and a "rethrow MVC exceptions" listener there, and inherit everywhere. This keeps setUp() boilerplate out of 200 test files.

Replace collaborators via the service manager, not via the container internals. This is the pattern that makes these tests maintainable:

$sm = $this->getApplicationServiceLocator();
$sm->setAllowOverride(true);
$sm->setService(PaymentGateway::class, $mockGateway);
$sm->setService(Clock::class, $fixedClock);

Do this before dispatch(). For authenticated flows, stub out your auth service the same way rather than wrestling with real sessions.

Smoke every GET route with a data provider. High value for near-zero cost — you catch broken view helpers, missing templates, container wiring errors and 500s across the whole app:

/** @dataProvider publicRoutes */
public function testPublicRoutesAreNotServerErrors($url)
{
    $this->dispatch($url);
    $this->assertNotResponseStatusCode(500);   // 200/302/403 all fine
}

Aim the assertion at "not a 500" and separately assert exact behaviour for the routes that matter. This catches the class of bug that unit tests never see.

Use url() for link/redirect expectations so you are not hand-writing paths that break when a route's __NAMESPACE__ or base path changes:

$this->dispatch('/orders/42/edit');
$this->assertQueryContentContains('h1', 'Edit order');
$this->assertQuery('a[href="' . $this->url('order/view', ['id' => 42]) . '"]');

DOM assertions beat string matching. assertQuery / assertXpath parse the rendered response, so whitespace and attribute ordering stop mattering. assertXpathContentContains, assertQueryCount and their variants cover most view-level checks. Keep a couple of assertResponseStatusCode and assertRedirectTo assertions alongside them so failures tell you which layer broke.

Console tests. Extend AbstractConsoleControllerTestCase, set the application config in setUp(), dispatch the console route/params, then assert on getResponse()->getContent() and the exit status. It's the same reset() discipline as HTTP.

Integration tips: wrap DB-touching tests in a transaction you roll back in tearDown() (this package gives you no fixtures or rollback — bring your own, e.g. zend-db or Doctrine DataFixtures), tag them @group integration so CI can run the fast unit suite separately, and cache the merged application config if bootstrapping dominates your runtime.

Gotchas and Tips

It is archived. Last release June 2019, no security or PHP 8 fixes. The successor is laminas/laminas-test — a mechanical migration: swap the Composer requirement, then Zend\Test\ → Laminas\Test\. On a legacy ZF3 app, pin the version and treat this as frozen infrastructure. On anything new, use Laminas.

PHPUnit version ceiling. These classes were written for the PHPUnit 4.8–7 era. On PHPUnit 8+ you must add : void to setUp()/tearDown() overrides, and very old releases simply won't install — check the constraint before blaming your test.

Call reset() between dispatches. Without it, request, response, route match and headers leak from the previous dispatch inside the same test method, producing assertions that pass or fail depending on order. Put it at the top of the test or in a tearDown(); setUp() ordering also matters — call setApplicationConfig() before parent::setUp().

A 500 tells you nothing until you unwrap the exception. ZF's MVC error handling swallows the real stack trace into an error page. Attach this in setUp() and you get the actual exception:

$this->getApplication()->getEventManager()->attach(
    \Zend\Mvc\MvcEvent::EVENT_DISPATCH_ERROR,
    function (\Zend\Mvc\MvcEvent $e) {
        if ($ex = $e->getParam('exception')) { throw $ex; }
    },
    100
);

For a mysterious 404, inspect $this->getRouteMatch() and $this->getRequest()->getUriString() — nine times out of ten it's a base-path or trailing-slash mismatch, because dispatch() takes the path directly and no server rewrite is involved.

assertQuery uses zend-dom's CSS support, which is limited. No :nth-child, flaky pseudo-selectors, nothing fancy. When a selector does not match, switch to assertXpath — it always works and the error messages are clearer.

dispatch($url, $method, $params, $isXmlHttpRequest) merges params into the request rather than mimicking a browser perfectly. For anything involving uploaded files, raw JSON bodies or specific content types, build the request by hand ($this->getRequest()->getFiles(), getContent(), headers) before dispatching. The fourth argument only adds X-Requested-With: XMLHttpRequest.

assertRedirectTo compares the Location header exactly. Behind a subdirectory deployment you'll want assertRedirectRegex('/\/dashboard$/') instead.

Sessions and globals survive between tests. $_SESSION, static caches and container singletons are shared across the process. Reset them explicitly, or you get order-dependent failures that only appear when the full suite runs.

Performance is the real tax. Every test constructs a fresh Application and re-merges config/application.config.php. Three practical mitigations: cache the merged config array and pass that to setApplicationConfig(), group the heavy tests, and avoid reset()-ing more than necessary within a single logical scenario.

Extension point: the cleanest customisation is a project-level AbstractWebTestCase that overrides setApplicationConfig()/getApplicationConfig() and pre-registers your standard mocks and the exception rethrow listener. Override getApplication() directly only when you genuinely need a non-standard bootstrap — and consider Zend\Test\Util\ModuleLoader standalone if you want the same bootstrapping outside PHPUnit, e.g. in a seeding or smoke-test script.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource