Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mocking same method with different "with" and different "return" throw an exception #4398

Open
oleg-andreyev opened this issue Aug 5, 2020 · 5 comments
Labels
feature/test-doubles Test Stubs and Mock Objects type/bug Something is broken

Comments

@oleg-andreyev
Copy link

oleg-andreyev commented Aug 5, 2020

Q A
PHPUnit version 9.2.6
PHP version 7.4.8
Installation Method Composer

Summary

Mocking same method with different "with" and different "return"

Current behavior

Expectation failed for method name is "getRepository" when invoked zero or more times
Parameter 0 for invocation Doctrine\ORM\EntityManager::getRepository('Op\AppBundle\Entity\Order') does not match expected value.
Failed asserting that two strings are equal.
Expected :'Op\AppBundle\Entity\Customer'
Actual   :'Op\AppBundle\Entity\Order'

How to reproduce

        $this->em
            ->method('getRepository')
            ->with(Customer::class)
            ->willReturn($this->customerRepository);

        $this->em
            ->method('getRepository')
            ->with(Order::class)
            ->willReturn($this->orderRepository);
$this->em->getRepository(Order::class)->findOneByOrderId($id);

image

Expected behavior

If "matcher" did not throw an exception on the second "match" (invoke), we should not throw an exception

@oleg-andreyev oleg-andreyev added the type/bug Something is broken label Aug 5, 2020
@sebastianbergmann sebastianbergmann added the feature/test-doubles Test Stubs and Mock Objects label Aug 5, 2020
@sebastianbergmann
Copy link
Owner

Thank you for your report.

Please provide a minimal, self-contained, reproducing test case that shows the problem you are reporting.

Without such a minimal, self-contained, reproducing test case I will not be able to investigate this issue.

@sebastianbergmann sebastianbergmann added the status/waiting-for-feedback Waiting for feedback from original reporter label Dec 28, 2020
@emdadulsadik
Copy link

emdadulsadik commented Mar 6, 2023

@sebastianbergmann , I have sucessfully reproduced the issue (PHPUnit 9.6.3 by Sebastian Bergmann and contributors.) To reproduce you can use TestCase and mock the doctrine's entity manager, have the entity manager match the getRepository method with two repository classes consecutively (order does not matter); you will see the matcher exception. Example:

// doctrine entities
class Order {}
class Customer {}

// doctrine repositories
class OrderRepository {}
class CustomerRepository {}

// Test Subject

class Order {

        public function __construct(EntityManagerInterface $em) {
             $order = $this->$em->getRepository(OrderRepository::class)->findBy(['id' => 1]);
        }
}


class OrderTest extends TestCase
{

        protected function setUp(): void {
               $this->em = $this->createMock(EntityManagerInterface::class);
               $this->customerRepositoryMock = $this->createMock(CustomerRepository::class);
               $this->orderRepositoryMock = $this->createMock(OrderRepository::class);  

               $this->em->method('getRepository')->with(Customer::class)
                       ->willReturn($this->customerRepositoryMock);
               
               $this->em->method('getRepository')->with(Order::class)
                       ->willReturn($this->orderRepositoryMock);
       }
}

// Then try test a class that uses these mock and try to get anything from these repositories,
// it will throw. 
// I hope I could make it reproducible for you. 

@oleg-andreyev
Copy link
Author

Thank you for your report.

Please provide a minimal, self-contained, reproducing test case that shows the problem you are reporting.

Without such a minimal, self-contained, reproducing test case I will not be able to investigate this issue.

Lol. It's been 2y+, somehow I've missed this notification. It's irrelevant for me atm. 💪👍

@sebastianbergmann
Copy link
Owner

Sorry, but something that uses Doctrine's entity manager is neither minimal nor self-contained.

@kubawerlos
Copy link
Contributor

Latest 10.5 branch:

kub@:~/code/sebastianbergmann/phpunit(10.5)$ cat test.php
<?php

interface I { public function get(string $x): int; }
class C1 {}
class C2 {}

class Test extends PHPUnit\Framework\TestCase
{
    private $mock;

    protected function setUp(): void
    {
        $this->mock = $this->createMock(I::class);
        $this->mock->method('get')->with(C1::class)
            ->willReturn(1);
        $this->mock->method('get')->with(C2::class)
            ->willReturn(2);
    }

    public function testSomething(): void
    {
        // 1 of the 2 lines below can be commented out and exception is still thrown
        $this->mock->get(C1::class);
        $this->mock->get(C2::class);

        $this->assertTrue(true);
    }
}

kub@:~/code/sebastianbergmann/phpunit(10.5)$ php phpunit test.php
PHPUnit 10.5.32-4-g8d77a5c4e by Sebastian Bergmann and contributors.

Runtime:       PHP 8.3.11
Configuration: /home/kuba/code/sebastianbergmann/phpunit/phpunit.xml

F                                                                   1 / 1 (100%)

Time: 00:00.005, Memory: 8.00 MB

There was 1 failure:

1) Test::testSomething
Expectation failed for method name is "get" when invoked zero or more times
Parameter 0 for invocation I::get('C1'): int does not match expected value.
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'C2'
+'C1'

/home/kuba/code/sebastianbergmann/phpunit/src/Framework/MockObject/Runtime/Matcher.php:110
/home/kuba/code/sebastianbergmann/phpunit/src/Framework/MockObject/Runtime/InvocationHandler.php:109
/home/kuba/code/sebastianbergmann/phpunit/test.php:23
/home/kuba/code/sebastianbergmann/phpunit/src/Framework/TestCase.php:1188
/home/kuba/code/sebastianbergmann/phpunit/src/Framework/TestCase.php:687
/home/kuba/code/sebastianbergmann/phpunit/src/Framework/TestRunner.php:106
/home/kuba/code/sebastianbergmann/phpunit/src/Framework/TestCase.php:517
/home/kuba/code/sebastianbergmann/phpunit/src/Framework/TestSuite.php:380
/home/kuba/code/sebastianbergmann/phpunit/src/TextUI/TestRunner.php:64
/home/kuba/code/sebastianbergmann/phpunit/src/TextUI/Application.php:202

--

There was 1 risky test:

1) Test::testSomething
This test did not perform any assertions

/home/kuba/code/sebastianbergmann/phpunit/test.php:20

FAILURES!
Tests: 1, Assertions: 0, Failures: 1, Risky: 1.

Isn't the:

        $this->mock->method('get')->with(C1::class)
            ->willReturn(1);
        $this->mock->method('get')->with(C2::class)
            ->willReturn(2);

incorrect way of mocking method and should be replaced with:

        $this->mock->method('get')->willReturnCallback(fn (string $x) => match (true) {
            $x === C1::class => 1,
            $x === C2::class => 2,
            default => throw new LogicException(),
        });

I wonder?

@sebastianbergmann sebastianbergmann removed the status/waiting-for-feedback Waiting for feedback from original reporter label Sep 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature/test-doubles Test Stubs and Mock Objects type/bug Something is broken
Projects
None yet
Development

No branches or pull requests

4 participants