Skip to content
Advertisement

PHPUnit: Best practice for when to use Mock Object method invocation matcher

I have been reading the PHPUnit docs online and have been using the mock object matchers but I am not sure when you should or shouldn’t use them. For the example below, let’s say I have a class Foo I am writing a test for:

Foo class

<?php

class Foo {
     public function isUserNamedTom(User $user):bool
     {
          return strtoupper($user->getName()) === 'TOM'; 
     }
}

My Test

<?php

use PHPUnitFrameworkTestCase;

class FooTest extends TestCase {
     public function testIsUserNamedTom():void
     {
          $userMock = $this->getMockBuilder(User:class)
          ->disableOriginalConstructor()
          ->getMock();

          $userMock->expects($this->once())
          ->method('getName')
          ->willReturn('tom');

          $fooService = new Foo();
          $response = $fooService->isUserNamedTom($userMock);

          $this->assertTrue($response);
     }
}

My question is should I be using $this->once() or not in this example and if I am not then what are the reasons.

Advertisement

Answer

Your tests should test the output of the method under test with as little knowledge as possible about the implementation of the method under test. You want the test to fail when the method no longer does what it’s supposed to do, but you don’t want it to fail when the method does the right thing in a different way.

In this case it seems irrelevant for the test of isUserNamedTom(User $user) whether user->getName() is called once, twice or never. The only important thing is that isUserNamedTom(User $user) returns true if and only if $user is named “tom”, “Tom”, “tOm”, etc.

Therefore: No, I wouldn’t check for $this->once() here.

I would even try to get by without mocking and pass an instantiated user object instead. But whether this is possible depends on your User class.

 public function testIsUserNamedTom():void
 {
      $user = (new User())->setName('tom');
      $this->assertTrue($fooService->isUserNamedTom($user));
 }

Basically, when writing tests, it’s always good to ask yourself under what circumstances you want them to fail. In your example, my guess is that your test shouldn’t fail if someone refactored isUserNamedTom(User $user) so that the method no longer calls $user->getName() (but maybe uses a public property $user->name).

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement