I want to unit test a wrapper class for the official Segment PHP integration. Therefore I'll have to mock the Segment class with Mockery so there won't be any real API requests.
The Problem
The class to mock consists only of static methods. Because of this I try to mock it like this (with alias):
$segment = Mockery::mock('alias:Segment');
This works, but only if the class isn't autoloaded by composer. If I load it - like I have to for the rest of the app - I'll get the error
Could not load mock Segment, class already exists.
(That makes sense, because the docs state that an aliased class must not be loaded before.)
The question
How can I mock this (evil?) class, but still use it as usual in the rest of my app?
Essentially, you cannot mock classes with static calls.
Static calls always reference exactly the class and method to be called, which is equivalent to pointing to exactly the file and line of code to be executed (if you assume basic autoloading capability is available).
The only way to execute different code is to NOT having included the original class, but to load the mock class code first. It does not matter if you have an alternative code file, or if you are calling eval() by using Mockery. Both ways will work.
But they will also work only one time. You cannot switch back to the original code in a later test, because the class can only be defined once per script run. And not being able to switch implementations (like original vs. mocked vs. another mocked) is the problem here.
The solution, which also was mentioned in the comments: Don't have classes with static methods. Always create instances of classes and call dynamic methods. This way you can easily mock the class, but it requires to create an instance first, and offer a way to inject the class (or at least the mocked class) into the code you want to test.
As a generic pattern I am using this if dependency injection is not available in the project (I have to deal with some legacy stuff at times):
public function __construct(MyClass $class = null) {
$this->class = $class ?: new MyClass();
}
This way I don't have to inject the class, but I would be able to inject a mock instead of the real class.
For situations where dependency injection is available, the constructor will be a very basic initializer:
public function __construct(MyClass $class) {
$this->class = $class;
}
This works great if your dependency injection framework is able to do auto wiring (like PHP-DI), and you only have exactly one MyClass, because this will be automatically injected without you having to define anything.