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 Answers2

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();
Sammitch
  • 30,782
  • 7
  • 50
  • 77
Alex Howansky
  • 50,515
  • 8
  • 78
  • 98
  • 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. – Meyer Auslander - Tst Jun 14 '23 at 19:13
  • 1
    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. – Alex Howansky Jun 14 '23 at 19:56
  • 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. – Meyer Auslander - Tst Jun 14 '23 at 21:03
  • 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');` – Alex Howansky Jun 14 '23 at 21:39
  • Whats wrong with using reflection in unit testing? If so, the production code will adhere more to the [KISS principal](https://dev.to/kwereutosu/the-k-i-s-s-principle-in-programming-1jfg), 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. – Meyer Auslander - Tst Jun 15 '23 at 19:25
  • Because: 1) KISS doesn't just apply to the "production code" -- it applies to the whole project. Adding one optional constructor parameter makes the required change to your test a single line instead of a paragraph of reflection. – Alex Howansky Jun 15 '23 at 20:24
  • 2) Write your code like somebody else is going to be maintaining it later. If you implement runtime reflection to change an attribute that's not supposed to be changed, then you break the expectation about how the class is supposed to work. Imagine a future coder looking at the class. "Oh hmm, why is this even an attribute? It never gets set anywhere, it's just a static string. I'll make it more KISS by changing it to a constant or hardcoding it." The class still works as desired but now your test breaks. – Alex Howansky Jun 15 '23 at 20:24
  • 3) If the optional constructor parameter offends you so much, then drop the constructor, set the default on the parameter definition itself, and add a `setFoo(callable $foo)` method that only ever gets used in the test. – Alex Howansky Jun 15 '23 at 20:26
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.

Sammitch
  • 30,782
  • 7
  • 50
  • 77
  • 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. – Meyer Auslander - Tst Jun 15 '23 at 19:37