Change record status: 
Project: 
Introduced in branch: 
9.3.x
Introduced in version: 
9.3.0
Description: 

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->connection->expects($this->at(2))
          ->method('query')
          ->with("SELECT 1 FROM pg_constraint WHERE conname = '$expected'")
          ->willReturn($this->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: https://github.com/sebastianbergmann/phpunit/issues/4297

Impacts: 
Module developers