skip to Main Content

In order to create a mock version of a global function for unit testing, I define its mock version in the namespace of the SUT class, as is explained here

Consider a case of global function foo() which is used in namespace Bar and in namespace Baz. If I want to use the same mock version of foo() in both places, it seems I am forced to make two declarations of the mock for foo()

/* bar-namespace-mocks.php */
namespace Bar;
function foo(){
   return 'foo mock';
}
/* baz-namespace-mocks.php */
namespace Baz;
function foo(){
   return 'foo mock';
}

This code does not conform to the DRY principal.
It would be preferred to have one declaration of the foo mock

/* foo-mock.php */
function foo(){
   return 'foo mock';
}

and then import into each namespace as needed like the following pseudo code:

/* namespace-mocks.php */
namespace Bar{
  import 'foo-mock.php';
} 
namespace Baz{
  import 'foo-mock.php';
}

Importing using include, e.g.

namespace Baz;
include 'foo-mock.php'

does not cause the mock to be declared in the Baz namespace. Is there any way to declare a function in more than one namespace without having more than one version of it?

2

Answers


  1. If you need to abstract away a native function, then make a contract for it, so that you can use dependency injection for the service it provides:

    interface FooInterface
    {
        public function get(): string;
    }
    

    Then provide a default implementation that uses the native function:

    class NativeFoo implements FooInterface
    {
        public function get(): string
        {
            return foo();
        }
    }
    

    Then your main class might look something like:

    class A
    {
        protected FooInterface $foo;
    
        public function __construct(FooInterface $foo = null)
        {
            $this->foo = $foo ?? new NativeFoo();
        }
    
        public function whatever()
        {
            $foo = $this->foo->get();
        }
    }
    

    So, you’ve got an optional argument in the constructor, and if you don’t provide a value, you’ll get the native foo function as a default. You’d just do:

    $a = new A();
    $a->whatever();
    

    But if you want to override that, you can do:

    $foo = new SomeOtherFoo();
    $a = new A($foo);
    $a->whatever();
    

    Then, in your test, you can build an explicit mock:

    class MockFoo implements FooInterface
    {
        public function get(): string
        {
            return 'some test value';
        }
    }
    

    And pass in instance of that:

    $foo = new MockFoo();
    $a = new A($foo);
    $a->whatever();
    

    Or use the PHPUnit mock builder:

    $foo = $this->getMockBuilder(FooInterface::class)->... // whatever
    

    If you want a simpler version, you could just use a callable:

    class A
    {
        protected $foo;
    
        public function __construct(callable $foo = null)
        {
            $this->foo = $foo ?? 'foo';
        }
    
        public function whatever()
        {
            $foo = call_user_func($this->foo);
        }
    }
    
    
    // default usage
    $a = new A();
    $a->whatever();
    
    // test usage
    $a = new A(fn() => 'some other value');
    $a->whatever();
    
    Login or Signup to reply.
  2. While I whole-heartedly agree with Alex Howansky’s answer, if you’re truly hell-bent on using plain functions, then you can always:

    namespace Mocks {
        function foo() {
            return 'foo';
        }
    }
    
    namespace Bar {
      function foo() {
          return Mocksfoo();
      }
    }
    
    namespace Baz {
      function foo() {
          return Mocksfoo();
      }
    }
    
    namespace asdf {
        var_dump(Barfoo(), Bazfoo());
    }
    

    Output:

    string(3) "foo"
    string(3) "foo"
    

    Though at this point we’re really just on our way to reinventing classes, but worse. At some point you’re going to wonder how to namespace a variable, and that is simply not possible, so you’ll be rolling this into classes at that point.

    Classes, interfaces, and traits/inheritance would be most properly leveraged to solve this problem.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search