How to Instantly Migrate Nette\Tester to PHPUnit

This post was updated at November 2020 with fresh know-how.
What is new?

Switch from deprecated --set option to rector.php config.

We had 🍺 after PHP meetup in Prague and Tomas asked me: <br> "We don't use Nette, but we still have many tests in Tester. Can Rector migrate them to PHPUnit?" <br> "Hold my 🍺"

In the last post we looked on instant migration of PhpSpec to PHPUnit.

PhpSpec has a different architecture than PHPUnit - e.g.

  • PHPUnit creates mocks in body of tested method $this->createMock() ,
  • but PhpSpec puts them in parameters public function is_should_work(Category $categoryMock).

This has a huge influence on the code, so it took a week to cover these differences in migration path.

How does Tester compare to PHPUnit?

In the last post we looked at absolute downloads and trends of 3 PHP unit test frameworks:

Putting numbers and trends aside - this is about your needs. Do you need to change from Doctrine to Eloquent? From Symfony 4.2 to Laravel 5.8? From Symfony to Nette? Go for it, Rector will help you with the boring PHP work you'd love to skip.

The guy in the pub that night needed this, so... challenge accepted!

Single Test Case

Luckily, Tester and PHPUnit are like twins:

  • share the same approach in configuring tests - setUp & tearDown
  • do assert with a call - Assert::true($value) vs. self::assertTrue($value)
  • do share naming - public function testSomething()
  • do share data providers - @dataProvider

So all we need to do is rename a few methods? There are still a few gotchas:

  • Assert::exception() uses code inside anonymous function, while in PHPUnit it's just above the code that should fail
  • Tester includes bootstrap itself, PHPUnit include in phpunit.xml
  • Tester creates the test under the test (new SomeTest())->run(), PHPUnit creates them automatically

Luckily, last 2 operations are subtractions, so we can just remove them.

And the Result?


 namespace App\Tests;

 use App\Entity\SomeObject;
-use Tester\Assert;
-use Tester\TestCase;

-require_once __DIR__ . '/bootstrap.php';

-class ExpensiveObjectTest extends TestCase
+class ExpensiveObjectTest extends \PHPUnit\Framework\TestCase
     public function testSwitches()
-        Assert::false(5);
+        $this->assertFalse(5);

-        Assert::falsey('value', 'some message');
+        $this->assertFalse((bool) 'value', 'some message');

-        Assert::truthy(true);
+        $this->assertTrue(true);

     public function testTypes()
         $value = 'x';
-        Assert::type('array', $value);
+        $this->assertIsArray($value);

-        Assert::type(SomeObject::class, $value);
+        $this->assertInstanceOf(SomeObject::class, $value);

     public function testException()
         $someObject = new SomeObject;
-        Assert::exception(function () use ($someObject) {
-            $someObject->setPrice('twenty dollars');
-        }, InvalidArgumentException::class, 'Price should be string, you know');
+        $this->expectException(InvalidArgumentException::class);
+        $this->expectExceptionMessage('Price should be string, you know');
+        $someObject->setPrice('twenty dollars');

+    /**
+     * @doesNotPerformAssertions
+     */
     public function testNoError()
-        Assert::noError(function () {
-             new SomeObject(25)
-        });
+        new SomeObject(25);

-(new ExpensiveObjectTest())->run();

How to Instantly Migrate from Nette\Tester to PHPUnit?

  1. Install Rector
composer require rector/rector --dev
  1. Add rector.php config:
use Rector\Set\ValueObject\SetList;
use Rector\Config\RectorConfig;

return function (RectorConfig $rectorConfig): void {
  1. Run it on /tests directory
vendor/bin/rector process tests

Then take few minutes to polish the details that Rectors missed and send the PR to your project ✅

Happy coding!

Do you learn from my contents or use open-souce packages like Rector every day?
Consider supporting it on GitHub Sponsors. I'd really appreciate it!