8

How do you write a unit test for a method that calls other methods of the same class, but doesn't return a value? (Let's say with PHPUnit.)

For example, let's say that I have the following class:

class MyClass {

    public function doEverything() {

        $this->doA();
        $this->doB();
        $this->doC();

    }

    public function doA() {
        // do something, return nothing
    }

    public function doB() {
        // do something, return nothing
    }

    public function doC() {
        // do something, return nothing
    }

}

How would you test doEverything()?

EDIT:

I'm asking this because from what I've read it seems like pretty much every method should have its own dedicated unit test. Of course, you also have functional and integration tests, but those target specific routines, so to speak (not on a per method level necessarily).

But if pretty much every method needs its own unit test, I'm thinking it would be "best practice" to unit test all of the above methods. Yes/no?

Krzysztof Safjanowski
  • 7,292
  • 3
  • 35
  • 47
Pete
  • 7,289
  • 10
  • 39
  • 63
  • Would unit tests even be considered at the point? One could argue that unit testing is less important than other types of testing, such as integration testing which may be what you'd be better at testing for given this scenario. – Anthony Forloney Aug 09 '14 at 01:40
  • Sounds good to me! Is there a rule of thumb regarding what to write unit tests for vs. functional/integration and acceptance tests? I was under the impression that pretty much every method should have its own unit test associated with it regardless of whether or not they were also covered by the broader functional/integration tests and/or acceptance tests. – Pete Aug 09 '14 at 03:53
  • possible duplicate of [How can I unit test void functions?](http://stackoverflow.com/questions/21219942/how-can-i-unit-test-void-functions) – Jeroen Vannevel Aug 09 '14 at 09:35
  • Also take a look [here](http://stackoverflow.com/questions/17731234/how-to-test-that-no-exception-is-thrown/17731324#17731324). You should have 4 unit tests. – Jeroen Vannevel Aug 09 '14 at 09:37
  • Beautiful, thank you. While the threads you posted are certainly helpful, I don't think there's any harm in having this thread which shows specifically the PHPUnit syntax for accomplishing what I'm after? (Assuming I, or someone else, can figure it out :-) – Pete Aug 09 '14 at 15:59

2 Answers2

13

Okay! I've figured it out! As might be expected, mocking is what I need in this situation--and mocking a sibling method is called partial mocking. There's some pretty great info about PHPUnit mocking in this article by Juan Treminio.

So to test doEverything() in the above class, I would need to do something like this:

public function testDoEverything()
{

    // Any methods not specified in setMethods will execute perfectly normally,
    // and any methods that ARE specified return null (or whatever you specify)
    $mock = $this->getMockBuilder('\MyClass')
        ->setMethods(array('doA', 'doB', 'doC'))
        ->getMock();

    // doA() should be called once
    $mock->expects($this->once())
         ->method('doA');

    // doB() should be called once
    $mock->expects($this->once())
         ->method('doB');

    // doC() should be called once
    $mock->expects($this->once())
         ->method('doC');

    // Call doEverything and see if it calls the functions like our
    // above written expectations specify
    $mock->doEverything();
}

That's it! Pretty easy!

BONUS: If you use Laravel and Codeception...

I'm using the Laravel Framework as well as Codeception, which made it a little bit trickier to figure out. If you use Laravel and Codeception you'll need to do a little bit more to get it working, since the Laravel autoloading doesn't by default connect into the PHPUnit tests. You'll basically need to update your unit.suite.yml to include Laravel4, as shown below:

# Codeception Test Suite Configuration

# suite for unit (internal) tests.
class_name: UnitTester
modules:
    enabled: [Asserts, UnitHelper, Laravel4]

Once you've updated your file, don't forget to call php codecept.phar build to update your configuration.

Pete
  • 7,289
  • 10
  • 39
  • 63
3

While your mocking test does achieve your goal, I would argue that you've decreased confidence in the code. Compare the original trivial method to the complicated method that tests it. The only way the method under test can fail is by forgetting to add one of the method calls or mistype a name. But you're now doubly-likely to do that with all that additional code, and it doesn't have any tests!

Rule: If your test code is more complicated than the code under test, it needs its own tests.

Given the above, you're better off finding another way to test the original code. For the method as written--three method calls with no parameters--inspection by eyeball is sufficient. But I suspect that the method does have some side-effects somewhere, otherwise you could delete it.

Unit testing is about testing the class as a unit, not each method individually. Testing each method alone is a good indication that you're writing your tests after the code. Employing Test Driven Development and writing your tests first will help you design a better class that is more-easily testable.

David Harkness
  • 35,992
  • 10
  • 112
  • 134