Internal Overview

The guide for developers contributing to AsyncUnit.

Thanks for taking an interest in contributing to AsyncUnit! Because this guide is for contributors to the library itself some assumptions are made about the audience of this document.

  • You understand how to write tests for AsyncUnit and have those tests run on the command line.

  • You understand how Labrador Core, Labrador AsyncEvent, and Amp work. Especially the Plugin and Event systems provided by the Labrador libraries.

  • A familiarity with the PHP-Parser project.


The library is effectively split into two modules in a monolithic repository. The first module is the framework module; it can be found in the framework_src/ directory and includes everything in the Cspray\Labrador\AsyncUnit namespace. The second module is the cli module; it can be found in the cli_src/ directory and includes everything in the Cspray\Labrador\AsyncUnitCli namespace. Each module also has a corresponding framework_test/ or cli_test/, respectively, where the test suite for each module is located. The test suites utilize PHPUnit to ensure that each module behaves as it is expected to.

The Framework

This is, if it wasn't obvious, the most important module. It constitutes the bulk of the functionality; everything needed to analyze your tests, instantiate the appropriate objects, invoke the correct hooks, and emit events about the processing of those tests. By itself, however, the framework will only execute your test suite; it does not provide any output about what has happened and would be inconvenient to run. That requires a client that knows how to interact with the framework. The bundled CLI will likely be the first client you use to run tests.


The CLI is a client of the framework that knows how to run tests from the context of a terminal or console. It is likely going to the client you use most often to interact with the framework and is currently the only supported client available. This is a Symfony Console application that, at the moment, is minimal in nature and only allows running a set of tests specified in 1 or more directories. Future versions will increase the functionality of this feature.

The Framework

From a high level the framework is split into two stages; a compilation phase and a runtime phase. The goal of the compilation phase is to parse the tests in the directories you have configured into a set of models that represent what code should actually be executed during runtime. The goal of the runtime phase is to instantiate TestSuites and TestCases, invoke appropriate hooks and tests, and emit events related to the execution of the actual test code based on the models that were output from the compilation phase.

The Compilation Phase

The compilation phase is one of the first things that happens when you execute the CLI tool and should happen fairly early in any custom clients. Simply put, the library can't do much without the models that should be generated during this phase! The public API of the compilation phase is very simple; you should be utilizing a Cspray\Labrador\AsyncUnit\Parser\Parser implementation to initiate test compilation. By default the Cspray\Labrador\AsyncUnit\Parser\StaticAnalysisParser is used. This implementation makes use of PHP-Parser to statically analyze tests for the appropriate information needed to run them.

The StaticAnalysisParser delegates the responsibility for the actual parsing to other objects, all of these objects live in the Cspray\Labrador\AsyncUnit\Parser namespace. The first is the AsyncUnitModelNodeVisitor. It is responsible for interacting with the PHP-Parser API and converting the Node that are generated into AsyncUnit models. The second is the AsyncUnitModelCollector. The node visitor attaches all created models to this implementation. After all code has been traversed the collector can then ensure all the appropriate tests are associated to the appropriate test cases that are associated to the appropriate test suites. It also takes care of ensuring that some other things related to test suite and test case inheritance are taken care of. For example, if a TestSuite or TestCase are marked as disabled this implementation ensure that all the corresponding tests are also marked disabled.

The Runtime Phase

The runtime phase is what happens after the Parser is done and is fully asynchronous. Where compilation's responsibilities are split between 2 classes the responsibility for the runtime phase lies solely with the Cspray\Labrador\AsyncUnit\TestSuiteRunner. This class knows how to instantiate and invoke the correct tests and hooks based on the ParserResult generated from the compilation phase. Based on the outcome of these test invocations a series of events will be emitted that informs you about what has happened. Please check out the "Events" Reference for more information.


At this point the CLI is a fairly simple Symfony Console project that determines which tests to parse, either from arguments to the run command, or based on a configuration if using the default command. Ultimately the documentation for Symfony Console is outside the scope of this guide and understanding the CLI is ensuring you know how both the Console package and the AsyncUnit framework operate.