I have situation like this. I have some 3rd party trait (I don't want to test) and I have my trait that uses this trait and in some case runs 3rd party trait method (in below example I always run it).
When I have code like this:
use Mockery;
use PHPUnit\Framework\TestCase;
class SampleTest extends TestCase
{
/** @test */
public function it_runs_parent_method_alternative()
{
$class = Mockery::mock(B::class)->makePartial();
$class->shouldReceive('fooX')->once();
$this->assertSame('bar', $class->foo());
}
protected function tearDown()
{
Mockery::close();
}
}
trait X {
function foo() {
$this->something->complex3rdpartyStuff();
}
}
trait Y2 {
function foo() {
$this->fooX();
return 'bar';
}
}
class B {
use Y2, X {
Y2::foo insteadof X;
X::foo as fooX;
}
}
it will work fine however I don't want code to be organized like this. In above code in class I use both traits but in code I want to test in fact trait uses other trait as mentioned at the beginning.
However when I have code like this:
<?php
use Mockery;
use PHPUnit\Framework\TestCase;
class SampleTest extends TestCase
{
/** @test */
public function it_runs_parent_method()
{
$class = Mockery::mock(A::class)->makePartial();
$class->shouldReceive('fooX')->once();
$this->assertSame('bar', $class->foo());
}
protected function tearDown()
{
Mockery::close();
}
}
trait X {
function foo() {
$this->something->complex3rdpartyStuff();
}
}
trait Y {
use X {
foo as fooX;
}
function foo() {
$this->fooX();
return 'bar';
}
}
class A {
use Y;
}
I'm getting:
undefined property $something
so it seems Mockery is not mocking in this case X::foo method any more. Is there are way to make possible to write such tests with code organized like this?
So far it is not possible to mock deeper aliased methods. You can proxy aliased method call using local method and allowing mocking protected methods.
Check code below
use Mockery;
use PHPUnit\Framework\TestCase;
class SampleTest extends TestCase
{
/** @test */
public function it_runs_parent_method()
{
$mock = Mockery::mock(A::class)->shouldAllowMockingProtectedMethods()->makePartial();
$mock->shouldReceive('proxyTraitCall')->once();
$this->assertSame('bar', $mock->foo());
}
protected function tearDown()
{
Mockery::close();
}
}
trait X {
function foo() {
$this->something->complex3rdpartyStuff();
}
}
trait Y {
use X {
foo as fooX;
}
function foo() {
$this->proxyTraitCall();
return 'bar';
}
function proxyTraitCall() {
return $this->fooX();
}
}
If you autoload trait you can try to overload it using Mockery.
/** @test */
public function it_runs_parent_method()
{
$trait = Mockery::mock("overload:" . X::class);
$trait->shouldReceive('foo')->once();
$class = Mockery::mock(A::class)->makePartial();
$this->assertSame('bar', $class->foo());
}
Don't test implementation details. Test it like you use it.
Class user have to know only public interface to use it, why test should be any different? Fact that one internal method call different one is implementation detail and testing this breaks encapsulation. If someday you will switch from trait to class method without changing class behaviour you will have to modify tests even though class from the outside looks the same.
From Pragmatic Unit Testing by Dave Thomas and Andy Hunt
Most of the time, you should be able to test a class by exercising its public methods. If there is significant functionality that is hidden behind private or protected access, that might be a warning sign that there's another class in there struggling to get out.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With