Page MenuHomePhabricator

Writing Unit Tests
Phabricator Contributor Documentation (Developer Guides)

Simple guide to libphutil, Arcanist and Phabricator unit tests.

Overview

libphutil, Arcanist and Phabricator provide and use a simple unit test framework. This document is aimed at project contributors and describes how to use it to add and run tests in these projects or other libphutil libraries.

In the general case, you can integrate arc with a custom unit test engine (like PHPUnit or any other unit testing library) to run tests in other projects. See Arcanist User Guide: Customizing Lint, Unit Tests and Workflows for information on customizing engines.

Adding Tests

To add new tests to a libphutil, Arcanist or Phabricator module:

  • Create a __tests__/ directory in the module if it doesn't exist yet.
  • Add classes to the __tests__/ directory which extend from PhabricatorTestCase (in Phabricator) or PhutilTestCase (elsewhere).
  • Run arc liberate on the library root so your classes are loadable.

Running Tests

Once you've added test classes, you can run them with:

  • arc unit path/to/module/, to explicitly run module tests.
  • arc unit, to run tests for all modules affected by changes in the working copy.
  • arc diff will also run arc unit for you.

Example Test Case

Here's a simple example test:

class PhabricatorTrivialTestCase extends PhabricatorTestCase {

  private $two;

  public function willRunOneTest($test_name) {
    // You can execute setup steps which will run before each test in this
    // method.
    $this->two = 2;
  }

  public function testAllIsRightWithTheWorld() {
    $this->assertEqual(4, $this->two + $this->two, '2 + 2 = 4');
  }

}

You can see this class at PhabricatorTrivialTestCase and run it with:

phabricator/ $ arc unit src/infrastructure/testing/testcase/
 PASS   <1ms*  testAllIsRightWithTheWorld

For more information on writing tests, see PhutilTestCase and PhabricatorTestCase.

Database Isolation

By default, Phabricator isolates unit tests from the database. It makes a crude effort to simulate some side effects (principally, ID assignment on insert), but any queries which read data will fail to select any rows and throw an exception about isolation. In general, isolation is good, but this can make certain types of tests difficult to write. When you encounter issues, you can deal with them in a number of ways. From best to worst:

  • Encounter no issues; your tests are fast and isolated.
  • Add more simulated side effects if you encounter minor issues and simulation is reasonable.
  • Build a real database simulation layer (fairly complex).
  • Disable isolation for a single test by using LiskDAO::endIsolateAllLiskEffectsToCurrentProcess(); before your test and LiskDAO::beginIsolateAllLiskEffectsToCurrentProcess(); after your test. This will disable isolation for one test. NOT RECOMMENDED.
  • Disable isolation for your entire test case by overriding getPhabricatorTestCaseConfiguration() and providing self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK => false in the configuration dictionary you return. This will disable isolation entirely. STRONGLY NOT RECOMMENDED.