Darkside
Darkside

Reputation: 649

PHPUnit: How to save an arguments passed to stub method

Method setValue() In class SomeClass accepts arguments and no way to get this arguments back for make assertion.

Can I create a stub for method setValue() that allows save an arguments passed to this method?


class SomeClass {
  public function setValue($name, $value)
  {
    // do some stuff
  }
  public function doSomething(array $values)
  {
    foreach ($values as $name=>$value) {
      $this->setValue($name, trim($value));
    }
  }
}

class TestSomeClass extends PHPUnit_Framework_TestCase {
public function testDoSomething() { $mock = $this->getMock('SomeClass', array('setValue')); $mock->doSomething(array('v1'=>' string ')); // here need I need assert like this $this->assertEquals('string', $argumentPassedToSetValue); } }

Upvotes: 4

Views: 2811

Answers (4)

David Harkness
David Harkness

Reputation: 36562

In this simple case you can specify the argument you expect to be passed to setValue().

class TestSomeClass extends PHPUnit_Framework_TestCase
{
  public function testDoSomething()
  {
    $mock = $this->getMock('SomeClass', array('setValue'));
    $mock->expects($this->once())->method('setValue')->with('v1', 'string');
    $mock->doSomething(array('v1'=>'  string  '));
  }
}

I agree with edorian, however, that you're better off testing what comes out of the class via the public API. Testing the internal implementation directly means updating more tests every time you change or refactor it.

Upvotes: 1

edorian
edorian

Reputation: 38981

On how to test if the setting worked see @Gordons answer.

I'd like to argue that you don't need to test that though.

Your unit tests should make sure the public API of your class works as expected. You don't care (for the sake of testing) how your values are stored internally so you don't need to make assertions on it. Doing it that way also makes your tests only test what your class does and not how the class does it

The point is that you shouldn't have to change your tests when you change your class without affecting what it does

For the sake of the argument let's say SomeClass is something that in the end spits out HTML.

class SomeClass {
  public function setValue($name, $value)
  {
    // do some stuff
  }

  public function doSomething(array $values)
  {
    foreach ($values as $name=>$value) {
      $this->setValue($name, trim($value));
    }
  }

  public function createHTML() 
  {
    $return = "";
    foreach($this->values as $key => $value) { 
         $return .= "<div is='$key'>$value</div>"; 
    }
    return $return;
  }

}

If that is everything your class does an absolutely sufficient test for that class could look like this:

class SomeClassTest extends PHPUnit_Framework_TestCase {

    public function testHtmlGenerationWithTwoValuesSet() {
        $o = new SomeClass();
        $o->setValue("foo", "bar");
        $o->setValue("x", "y");
        $result = $o->createHTML();
        $this->assertSame(
             2,
             substr_count("<div>", $result),
             "There should be as many divs as there are values set"
        );
        $this->assertTrue(
             strpos("<div>bar</div>") !== false
             "String should contain a set value enclosed in divs"
        );
    }

}

Again: It is about testing behavior of your class, not testing every method on its own. Your test suite will be much more valuable if you got about it this way

While the example with html might not be the right one it shows how to test behavior pretty nicely (i hope)

Upvotes: 3

alfmartinez
alfmartinez

Reputation: 199

You can use returnCallback to execute arbitrary code like a test helper method of your testcase. For example, if setValueTester is a method in your testcase this expectation will redirect the call to setValue on the mock to it. Arguments are available with func_get_args.

$mock->expects($this->any())->method("setValue")->will($this->returnCallback(array($this,"setValueTester"));

Upvotes: 2

Gordon
Gordon

Reputation: 317177

No. Mocks are replacements for Dependencies and not for the actual TestSubject.

In case your setValue method sets non-public properties, you can use assertAttributeEquals:

class SomeClass
{
    protected $foo;
    public function setFoo($val)
    {
        $this->foo = trim($val);
    }
}

class SomeClassTest extends PHPUnit_Framework_TestCase
{
    public function testSetFooTrimsArgument()
    {
        $testSubject = new SomeClass;
        $testSubject->setFoo('  bar  ');
        $this->assertAttributeEquals(
            'bar',  /* expected value */
            'foo',  /* attribute name */
            $testSubject
        );
    }
}

More information on how to test privates can be found in

Upvotes: 2

Related Questions