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

PHPUnit at() matcher is deprecated #231

Open
mglaman opened this issue Apr 21, 2023 · 0 comments
Open

PHPUnit at() matcher is deprecated #231

mglaman opened this issue Apr 21, 2023 · 0 comments
Labels
9.3.x archived Issues that are unlikely to work on in the future

Comments

@mglaman
Copy link
Owner

mglaman commented Apr 21, 2023

https://www.drupal.org/node/3218874

Introduced in branch/version: 9.3.x / 9.3.0

The PHPUnit at() matcher, used to determine the order that methods are called on test doubles, has been deprecated. It will be removed in PHPUnit 10. You should refactor your tests to not rely on the order in which methods are invoked. The best replacement depends on how the method was used in the test previously:

  1. Order of method calls

    Where at() was used only to enforce the order of calls:

        $test_processor->expects($this->at(0))
          ->method('buildConfigurationForm')
          ->with($this->anything(), $form_state)
          ->will($this->returnArgument(0));
        $test_processor->expects($this->at(1))
          ->method('validateConfigurationForm')
          ->with($this->anything(), $form_state);
        $test_processor->expects($this->at(2))
          ->method('submitConfigurationForm')
          ->with($this->anything(), $form_state);
    

    We can just replace this with the number of times that the method should be called:

        $test_processor->expects($this->once())
           ->method('buildConfigurationForm')
           ->with($this->anything(), $form_state)
           ->will($this->returnArgument(0));
        $test_processor->expects($this->once())
           ->method('validateConfigurationForm')
           ->with($this->anything(), $form_state);
        $test_processor->expects($this->once())
           ->method('submitConfigurationForm')
           ->with($this->anything(), $form_state);
    
  2. Different with() and will() used on a single method

    Where different with() and will() values were used on a single method, and the test does not strictly care about the order of the calls:

        $language_manager->expects($this->at(0))
          ->method('getLanguage')
          ->with($this->equalTo($source))
          ->will($this->returnValue(new Language(['id' => 'en'])));
        $language_manager->expects($this->at(2))
          ->method('getLanguage')
          ->with($this->equalTo($source))
          ->will($this->returnValue(new Language(['id' => 'en'])));
        $language_manager->expects($this->at(3))
          ->method('getLanguage')
          ->with($this->equalTo($target))
          ->will($this->returnValue(new Language(['id' => 'it'])));
    

    We can convert this to withReturnMap():

        $language_manager->expects($this->any())
           ->method('getLanguage')
          ->willReturnMap([
            [$source, new Language(['id' => $source])],
            [$target, new Language(['id' => $target])],
          ]);
    
  3. Testing return values only for non-idempotent method calls

    Where the order of the calls is important and we only have return values, e.g. we are testing two different sets of expectations in the same method:

        $term_storage->expects($this->at(0))
          ->method('loadAllParents')
          ->will($this->returnValue([$term1]));
        $term_storage->expects($this->at(1))
          ->method('loadAllParents')
          ->will($this->returnValue([$term1, $term2]));
    

    We can test for the correct number of calls and use willReturnOnConsecutiveCalls():

        $term_storage->expects($this->exactly(2))
          ->method('loadAllParents')
          ->willReturnOnConsecutiveCalls(
            [$term1],
            [$term1, $term2],
          );
    
  4. Testing specific call order with specific parameters

    Where the order of calls is important and we have expectations about what is passed:

        $this->messenger->expects($this->at(0))
          ->method('addError')
          ->with('no title given');
        $this->messenger->expects($this->at(1))
          ->method('addError')
          ->with('element is invisible');
        $this->messenger->expects($this->at(2))
          ->method('addError')
          ->with('this missing element is invalid');
        $this->messenger->expects($this->at(3))
          ->method('addError')
          ->with('3 errors have been found: <ul-comma-list-mock><li-mock>Test 1</li-mock><li-mock>Test 2 &amp; a half</li-mock><li-mock>Test 3</li-mock></ul-comma-list-mock>');
    

    We can test for the correct number of calls and use withConsecutive():

        $this->messenger->expects($this->exactly(4))
           ->method('addError')
          ->withConsecutive(
            ['no title given', FALSE],
            ['element is invisible', FALSE],
            ['this missing element is invalid', FALSE],
            ['3 errors have been found: <ul-comma-list-mock><li-mock>Test 1</li-mock><li-mock>Test 2 &amp; a half</li-mock><li-mock>Test 3</li-mock></ul-comma-list-mock>', FALSE],
          );
    
  5. Testing specific call order with specific parameter and return values

    Where both the passed arguments and return values are important:

        $this->connection->expects($this->any())
          ->method('query')
          ->willReturn($statement);
    
    $this-&gt;connection-&gt;expects($this-&gt;at(2))
      -&gt;method('query')
      -&gt;with("SELECT 1 FROM pg_constraint WHERE conname = '$expected'")
      -&gt;willReturn($this-&gt;createMock('\Drupal\Core\Database\StatementInterface'));
    


    We can combine withConsecutive() and willReturnOnConsecutiveCalls():

        $this->connection->expects($this->exactly(2))
          ->method('query')
          ->withConsecutive(
            [$this->anything()],
            ["SELECT 1 FROM pg_constraint WHERE conname = '$expected'"],
          )
          ->willReturnOnConsecutiveCalls(
            $statement,
            $this->createMock('\Drupal\Core\Database\StatementInterface'),
          );
    

For more information please see the PHPUnit issue where the deprecation was introduced: sebastianbergmann/phpunit#4297

@mglaman mglaman added the 9.3.x label Apr 21, 2023
@bbrala bbrala added the archived Issues that are unlikely to work on in the future label Oct 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
9.3.x archived Issues that are unlikely to work on in the future
Projects
None yet
Development

No branches or pull requests

2 participants