Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use PHPUnit to test a method that calls other methods of the same class, but returns no value

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?

like image 916
Pete Avatar asked Aug 09 '14 01:08

Pete


People also ask

Which method is used to create a mock with PHPUnit?

PHPUnit provides methods that are used to automatically create objects that will replace the original object in our test. createMock($type) and getMockBuilder($type) methods are used to create mock object. The createMock method immediately returns a mock object of the specified type.

What is stub in PHPUnit?

Stub. Stubs are used with query like methods - methods that return things, but it's not important if they're actually called. $stub = $this->createMock(SomeClass::class); $stub->method('getSomething') ->willReturn('foo'); $sut->action($stub);

What is PHPUnit used for?

PHPUnit is a programmer-oriented testing framework for PHP. It is an instance of the xUnit architecture for unit testing frameworks. PHPUnit 9 is the current stable version. PHPUnit 10 is currently in development.


2 Answers

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.

like image 182
Pete Avatar answered Sep 23 '22 10:09

Pete


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.

like image 22
David Harkness Avatar answered Sep 24 '22 10:09

David Harkness