Hey!
So, this is one that I find super useful but completely understand if you're not a fan. Usually in larger projects, a mocked interface might have multiple public methods that get called in a feature test. You're only interested in testing one of those methods, but still need to provide a fallback for the other methods.
You might think "that's a partial mock", but I beg to differ because the partial mock might still be doing too much and may still require you to mock the methods/dependencies out.
This PR adds the idea of inheriting public class methods for a mock. You pass it a class, and it will look up its public methods and defer to those if no hard expectation has been set.
Example
First of all, we create an object. It doesn't have to implement anything, but it can if we want to force it to have all of the methods of the interface we're testing.
class BackupClass implements SomeInterface {
public function get() {
return 'Hello World';
}
public function post() {
return 'Foo Bar';
}
}
Now we write our test. We can call methods without using expect
, and it will use the method call from the inherited class instead.
$mock = mock(Http::class)->inherit(new BackupClass);
expect($mock->post('get'))->toBe('Hello World');
However, if we use an extend
call, it will opt for our overridden version:
$mock = mock(Http::class)
->inherit(new BackupClass)
->extend(post: fn() => "Hey Nuno!");
expect($mock->post('get'))->toBe('Hey Nuno!');
This gets even more powerful in feature tests, where other methods on the interface might be being called, but you're not interested in testing them at this moment.
$mock = mock(Http::class)
->inherit(new BackupClass)
->extend(post: fn() => "Hey Nuno!");
$this->swap(Http:class, $mock);
expect(someFeatureThatCallsGetButReturnsPost())->toBe('Hey Nuno!');
This implementation also allows you to provide multiple inherited classes, so you could mix and match methods together based on the use case required for the given test.
Let me know what you think. Hope you're having a good week!