Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining dependencies with data providers

Tags:

php

phpunit

I have one test method that depends on another method that itself uses a data provider in PHPUnit:

/** 
 * @dataProvider getFields
 */
public function testCanDoSomeStuff($parm1, $parm2) {
  $result = my_func($parm1, $parm2);
  $this->assertNotNull($result);

  return $result;
}

/**
 * @depends testCanDoSomeStuff
 */
public function testCanDoSomeMoreStuff($result) {
  $this->assertNotNull($result);
}

I also have a getFields() data provider function, no need to show that here.

The first test that relies on the data provider passes - $result is NOT null.

I expect that the result of the test will be passed to the dependent test as the $result parameter. However, the testCanDoSomeMoreStuff function receives a NULL parameter and the test fails.

Update

This simple test case demonstrates the problem:

class MyTest extends PHPUnit_Framework_TestCase {

    /**
     * @dataProvider myFunc
     */
    public function testCanDoSomeStuff($value) {
        $this->assertNotNull($value);
        return $value;
    }

    /**
     * @depends testCanDoSomeStuff
     */
    public function testCanDoSomeMoreStuff($value) {
        $this->assertNotNull($value);
    }

    /**
     * Data provider function
     */
    public function myFunc() {
      $values = array('22');
      return array($values);
    }
}

As a workaround for now, I've stored the result in a static property between tests.

like image 760
iainp999 Avatar asked Jul 24 '12 11:07

iainp999


2 Answers

The problem is the result of several factors:

  • Each test result is stored in an array using the test's name as the key.
  • The name for a test that receives data is <name> with data set #<x>.
  • The @depends annotation doesn't accept multiple words.

There is a hacky workaround: override TestCase::getDataSetAsString to return a name that the annotation will accept. This is made slightly problematic since the required TestCase fields are private, but with PHP 5.3.2+ you can get around that.

Important: Unfortunately, you cannot have the dependent test run for every data row--only one specific row. If your data provider returns only one row of data, this isn't an issue.

Here's the code with a sample test. Note that you don't have to name your data row. If you leave off the 'foo' key, change the @depends to testOne-0.

class DependencyTest extends PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider data
     */
    public function testOne($x, $y) {
        return $x + $y;
    }

    public function data() {
        return array(
            'foo' => array(1, 2),
        );
    }

    /**
     * @depends testOne-foo
     */
    public function testTwo($z) {
        self::assertEquals(3, $z);
    }

    protected function getDataSetAsString($includeData = false) {
        if (!$includeData && $this->getPrivateField('data')) {
            return '-' . $this->getPrivateField('dataName');
        }
        return parent::getDataSetAsString($includeData);
    }

    private function getPrivateField($name) {
        $reflector = new ReflectionProperty('PHPUnit_Framework_TestCase', $name);
        $reflector->setAccessible(true);
        return $reflector->getValue($this);
    }
}

Obviously, this is not a long-term solution. It would be better of you could have the dependent test run once for each test result from the method receiving the data. You could submit a feature request or pull request to PHPUnit.

like image 66
David Harkness Avatar answered Nov 09 '22 21:11

David Harkness


If your $result in testCanDoSomeStuff() is really not null, then this should work.

To take this apart, first try to simplify it without the data provider, something like this:

class StackTest extends PHPUnit_Framework_TestCase {
    public function testCanDoSomeStuff() {
        $result = true;
        $this->assertTrue($result); 
        return $result;
    }

    /**
     * @depends testCanDoSomeStuff
     */
    public function testCanDoSomeMoreStuff($result) {
        $this->assertNotNull($result);
    }
}

Testing this should result into something like...

~>phpunit test.php
PHPUnit 3.6.11 by Sebastian Bergmann.
..
Time: 1 second, Memory: 3.25Mb
OK (2 tests, 2 assertions)

Now add the data provider, replace my simple variable with your function and then test it again.

If this result differs, var_dump the variable $result before you return it in testcase testCanDoSomeStuff(). If it isn't null there, bug the behaviour.

like image 2
Bjoern Avatar answered Nov 09 '22 23:11

Bjoern