0

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 2

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();
Sign up to request clarification or add additional context in comments.

8 Comments

This is technically valid solution. Although, I would prefer to call the global function foo directly without creating a wrapper for it. I don't want code that is written in an unusual way just for the sake of unit testing it.
I wouldn't say it's unusual, that's how DI works. If you need to override a native function, other mechanisms will be hacky and likely break PSR-4. That said, I've updated with a simpler alternative.
Wouldn't it be simpler, instead of passing an object into the constructor, to simply directly set the protected member $foo to the default value. Then for unit testing reset it to a mock value using reflection.
Using reflection? Egads no, don't do that. Note, with the "simpler version" above, there's no object being instantiated or passed, it's just a callable, which can be a static string containing the name of a function. E.g., you could do $a = new A('fooOverrideFunction');
Whats wrong with using reflection in unit testing? If so, the production code will adhere more to the KISS principal, by not requiring any unnecessary input parameters. Even if we want to make the default $foo an object, it's no problem. But we don't need to complicate the class constructor with an DI input parameter.
|
0

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 \Mocks\foo();
  }
}

namespace Baz {
  function foo() {
      return \Mocks\foo();
  }
}

namespace asdf {
    var_dump(\Bar\foo(), \Baz\foo());
}

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.

1 Comment

Agreed this solution is more along the lines of what I asked for. To simply it more, we don't need a Mocks namespace. Just put a function called foo_mock() in the global namespace. Then call it from each of the namespace-delclared foo()'s. The code still seems repetitive. However, at least now, if an update needs to be made, there is only one version of foo_mock() to update.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.