How to Instantly Migrate PhpSpec to PHPUnit

I'm happy that more and more people try to use Rector upgrade and migrate their code-bases to the ones they really want for a long time.

Last week I was approached by 2 different people with single need - migrate their tests to PHPUnit.
"Nobody believes in anything without an inner feeling that it can be realized.
This is the only source of dreamlike powers."
Peter Altenberg

Disclaimer: I never used nor saw PhpSpec in code before this mentoring session. All I learned was from my client and his needs (and bit of reading the documentation).

"Why do you Have 2 Unit-Testing Frameworks?"

That was my question to one of my clients when I saw both PhpSpec and PHPUnit tests in its code base.

But before I noticed PhpSpec and asked about it, we had another chat:

Then I explored PhpSpec and found out, it's basically PHPUnit with different naming.

"It looks like Y, a variant of X, could be done in
about half the time, and you lose only one feature."
The Pragmatic Programmer book

Why did we choose to migrate PhpSpec tests to PHPUnit? Well, it's better obviously... Do you agree with me just because I wrote that? Don't do that, it's my personal opinionated opinion (= feeling, emotion). Ask me for some data instead.

Let's look at downloads:

But 117 mil. downloads can be like "You should use Windows XP because it's the most used Windows version ever!" That's classic manipulation of dying dinosaur.

Let's see the trends! In the same order:

Which one would you pick from this 2 information? I'd go for the last one, so did my client. So that's why we agreed to migrate PhpSpec (the middle one) to PHPUnit.


This is how 1 spec migration might look like:

 <?php

-namespace spec\App\Product;
+namespace Tests\App\Product;

-use PhpSpec\ObjectBehavior;

-final class CategorySpec extends ObjectBehavior
+final class CategoryTest extends \PHPUnit\Framework\TestCase
 {
+    /**
+     * @var \App\Product\Category
+     */
+    private $createMe;

-    public function let()
+    protected function setUp()
     {
-        $this->beConstructedWith(5);
+        $this->createMe = new \App\Product\Category(5);
     }

-    public function it_returns_id()
+    public function testReturnsId()
     {
-        $this->id()->shouldReturn(5);
+        $this->assertSame(5, $this->createMe->id());
     }

-    public function it_blows()
+    public function testBlows()
     {
-        $this->shouldThrow('SomeException')->during('item', [5]);
+        $this->expectException('SomeException');
+        $this->createMe->item(5);
     }

-    public function it_should_be_called(Cart $cart)
+    public function testCalled()
     {
+        /** @var Cart|\PHPUnit\Framework\MockObject\MockObject $cart */
+        $cart = $this->createMock(Cart::class);
-        $cart->price()->shouldBeCalled()->willReturn(5);
+        $cart->expects($this->atLeastOnce())->method('price')->willReturn(5);
-        $cart->shippingAddress(Argument::type(Address::class))->shouldBeCalled();
+        $cart->expects($this->atLeastOnce())->method('shippingAddress')->with($this->isType(Address::class));
     }

-     public function is_bool_check()
+     public function testBoolCheck()
      {
-         $this->hasFailed()->shouldBe(false);
+         $this->assertFalse($this->createMe->hasFailed());
-         $this->hasFailed()->shouldNotBe(false);
+         $this->assertNotFalse($this->createMe->hasFailed());
      }

-     public function is_array_type()
+     public function testArrayType()
      {
-         $this->shippingAddresses()->shouldBeArray();
+         $this->assertIsIterable($this->createMe->shippingAddresses());
      }
 }

Pretty clear, right?

How to Instantly Migrate from PhpSpec to PHPUnit?

First, take a 2-week paid vacation... Just kidding. Start with Rector which migrates ~95 % of code cases. It also renames *Spec.php to *Test.php and moves them from /spec to /tests directory:

composer require rector/rector --dev
vendor/bin/rector process spec --level phpspec-to-phpunit

Rector is getting smarter every set, but I bet there are still some missed cases. When the code doesn't work, just go and report the issue with expected vs. current output.

Take 10 more minutes to polish the rest of code and send PR to your project


And what was the other unit testing framework that Rector migrated? You'll see in the next post.


Happy coding!