How would you write a PHPUnit test for this function?

protected function runWithParamsInternal($object, $method, $params)
{
    $ps = [];
    foreach ($method->getParameters() as $i => $param) {
        $name = $param->getName();
        if (isset($params[$name])) {
            if ($param->isArray()) {
                $ps[] = is_array($params[$name]) ? $params[$name] : [$params[$name]];
            } elseif (!is_array($params[$name])) {
                $ps[] = $params[$name];
            } else {
                return false;
            }
        } elseif ($param->isDefaultValueAvailable()) {
            $ps[] = $param->getDefaultValue();
        } else {
            return false;
        }
    }
    $method->invokeArgs($object, $ps);
    return true;
}

How would you write a PHPUnit test for this function? I am trying to understand how to write unit test for complex functions you find in real life instead of making simple tests for getter and setter functions.

1 Like

Are familiar with mocks?
https://phpunit.de/manual/current/en/test-doubles.html
There are a couple of mocking libraries that phpunit supports. I like Prophecy.

In your case you would mock the method object, add in a test for getParameters and return the desired info then test again to vewrify that invoke is called with the proper $ps values. Should be pretty straight forward.

1 Like

Can you give me the code on how to do this properly?

Post what you have tried with mocks and maybe someone can help you. If you are wondering where to even start then the manual might be a good place.

I am just looking for an example similar to this, because the examples I found only cover really simple cases or examples that are completely out of touch with reality…

not at all. it’s a non-public method, so you cannot test it directly but have to test public methods that use this method.

not at all. it’s a non-public method, so you cannot test it directly but have to test public methods that use this method.

Absolutely correct. I overlooked the protected keyword in the method definition. Only test the public interface.

What if we have a private/protected method we want to test? Should it be refactored out to a separate class and become public in that context?

A proper unit test of the class should include running values through the public interface that test all of the private/protected methods that the public methods call. The combination of unit tests should test every path through the code in the class at least once.

this is something that can be checked with code coverage.

What if we have a private/protected method we want to test? Should it be refactored out to a separate class and become public in that context?

That is pretty much the basic idea. Unit tests are really only about what the class does. How it does it should not be a concern. Ideally, the class would be designed to be testable right from the start.

Having said that, I do admit to cheating every now and then by using reflection to access private/protected methods directly from a test. Sometimes you just need to get things done.

[quote=“ahundiak, post:11, topic:217358”]
That is pretty much the basic idea. Unit tests are really only about what the class does. How it does it should not be a concern.[/quote]
Yes, but if I want to test a private method this doesn’t mean I want to test how it does what it does. I agree that for outside callers private methods are of no interest and will not be tested by them - but private methods are certainly of interest to inside callers, which are other methods of the same class. By analogy, we would need to include unit tests inside the class to test the private/protected methods. How would it end up in practice? I imagine bloated classes with unit test methods - but maybe we could attach those unit tests with traits or inject by some reflection magic?

I mean we may get the assumption that private methods are not to be tested - but they also may do stuff that we want to test and it would be important to test them. Testing them through the available public methods will not always cut it since we are not communicating with them directly but though a proxy or a chain of proxies and the tests may not be able to test them as thoroughly as would be necessary.

Why? The inside methods are presumably called from the public ones as that’t the only way to access them. If you ensure you have unit tests that test every path through the code then some of those tests of the public methods will fully test those that are private as well - at least for all the types of data that can be passed to those private methods.

It is not necessary to test the private methods with invalid values that the public methods have already validated out as there is no way for those to get into the private methods.

Testing the private methods outside of the class is not unit testing as the entire class is the unit and all the test that pass values to the private method that are needed for the unit test can be done via the public method testing.

Testing them through the available public methods will not always cut it since we are not communicating with them directly but though a proxy or a chain of proxies and the tests may not be able to test them as thoroughly as would be necessary.

I suspect we may have a terminology issue here. I am having trouble visualizing a class that cannot be tested through it’s public interface. Perhaps you could provide an example?

And I don’t understand what proxy means in this context. Unit tests almost always directly instantiates the object to be tested and then calls methods directly on it.

Surely it would be easier to provide examples of unicorns and flying pigs.

Classes can ONLY be accessed through their public interface and so no matter what the access chain involves the private methods can only be accessed from the public ones - that’s what public and private mean.

As for protected - they are private to the classes that have inherited them from the original class and so will be tested by the unit testing of those classes.

Making methods private or protected makes it unnecessary to test them directly as the sole purpose in doing that is to remove access to them from anywhere other than the public methods. Unit testing the public methods of a class properly automatically includes all the testing required for the private methods of that class.

After all the class can always be refactored in the future such that the private method no longer exists as it can’t be accessed from outside the class and is there purely because the current implementation of the class makes it useful. How would you retest something that doesn’t exist next time you unit test?

By proxy I meant public methods. If there are private methods then they are not tested directly but only through the public methods. A simple example:

/**
 * Loads data from form fields into array and perform necessary conversions.
 */
class FormDataLoader {

    public function load(array $formData) {
        $formData = $this->trimElements($formData);
        $formData = $this->emptyStringsToNull($formData);
        
        return $formData;
    }
    
    /**
     * Trim whitespace recursively.
     */
    private function trimElements(array $data) {
        foreach ($data as $key => $value) {
            if (is_string($value)) {
                $data[$key] = trim($value);
                
            } elseif (is_array($value)) {
                $data[$key] = $this->trimElements($value);
            }
        }
        
        return $data;
    }
    
    /**
     * Convert empty strings to null values recursively.
     */
    private function emptyStringsToNull(array $data) {
        foreach ($data as &$val) {
            if ($val === '') {
                $val = null;
                
            } elseif (is_array($val)) {
                $val = $this->emptyStringsToNull($val);
            }
        }
        return $data;
    }
}

I couldn’t find a perfect example to illustrate the problem but this is the general idea - there are two private methods trimElements() and emptyStringsToNull(), which are not normally tested directly but through the public load() method. This means that we are testing the private methods through a proxy - and more specifically, we don’t even know if we are testing them because they are hidden to the outside scope.

In this example, we can simply test the public load() method and pass it multidimensional arrays with some space-padded and empty elements to check if the necessary trim and string to null conversions are applied properly. But wouldn’t it be simpler and easier to be able to test trimElements() and emptyStringsToNull() directly? If we did then the tests would be smaller, more specialized and we would test only one thing at a time instead of a number of things. Again, this example is quite simple and the problem is not so pronounced but I imagine there are cases where having more isolated tests would simplify things. Another benefit would be that we could take such a private method along with its unit test and use it easily within another project.

I don’t know if this is a big enough problem that needs solving in practice or it’s enough to focus on the public methods and be done with it. I just thought that if a method does something useful then regardless of its visibility it would be useful to test it.

Probably you are right. The problem with testing private methods is where to put the tests? They cannot be regular unit tests run from the outside - if they existed they would need to be run from the inside, which means they would need to become part of the class. Perhaps that would be too extreme?

That’s exactly what they have to be.

Unit testing is the only level of testing where you actually care about how the internals of the class work in so far as making sure that the tests actually cover all if the different combinations that will ensure all paths through all the code get run in at least one test.

If there is a path that can’t be run via a unit test then it shouldn’t nbe there as it can’t be run at all.

While in theory I agree with @felgall on that you should test public methods. But in some cases, this makes very messy and complicated tests.

In the cases you find yourself having too many test cases you need to apply to the public method and you want to test the protected/private methods you can:

  1. Use reflection to change the method to public accessible. (Good example)
  2. Use protected instead of private and extend the class (messy solution, use no. 1 instead!)

If it does then the class is too complicated and needs to be refactored.

Anyway, you only have to set up the unit tests once and rerun them each time the class changes. Using your approach changes to remove or change the parameters for a private method would break the unit test.

This is very true, but refactoring is a luxury that is not always available.

The option I mentioned, is quite useful when the company take over a project that someone else worked on (i.e. get a new client, with existing code base etc.). In many of these scenarios, the code base come with none or very few unit tests.

In these cases, you usually have a problem to fix, feature to add etc. without the time available to refactor the code base, nor do you necessarily understand the domain knowledge enough to make justified refactoring at this stage.

This is where being able to add unit tests to private or protected methods as modifications are done help, and slowly the project will get higher coverage.