0

Given the following Value object (no publicly accessible setters):

class Address
{
    public function __construct(string $addressLine1, string $addressLine2 = null, string $town, string $county, PostcodeInterface $postcode)
    {
        if (!$this->validateAddressLine($addressLine1)) {
            throw new \InvalidArgumentException(...);
        }
        $this->addressLine1 = $this->normaliseAddressLine($addressLine1);
        ...
    }

    private function validateAddressLine(string $addressLine)
    {
        ...
    }

    private function normaliseAddressLine(string $addressLine)
    {
        ...
    }
}

I have the following test class:

class AddressTest extends PHPUnit\Framework\TestCase
{
    public function invalidConstructorArgs()
    {
        return [
            ['1/1 Greenbank Road', '%$', 'city', 'county', new Postcode('123FX')]
        ];
    }

    /**
     * @test
     * @dataProvider invalidConstructorArgs
     */
    public function constructor_with_invalid_args_throws_exception($addressLine1, $addressLine2, $town, $county, $postcode)
    {
        $this->expectedException(\InvalidArgumentException::class);
        $address = new Address($addressLine1, $addressLine2, $town, $county, $postcode);
    }
}

As you can see I am currently using a DataProvider to supply my unit test with data. Doing so results in a large array of values to be tested which are all written manually. Each argument is validated by an appropriate private method. Currently to test those methods I am writing a data provider which contains valid and invalid argument values to test these methods (as below):

// test validation on first argument
["invalid", "valid", "valid", "valid", "valid"],
...
// test validation on second argument
["valid", "invalid", "valid", "valid", "valid"],
...
// test validation on third argument
["valid", "valid", "invalid", "valid", "valid"],
...
// etc.

Is there something in PHPUnit I should be making use of that I have overlooked for this type of scenario?

7
  • You're using the right thing. The values should be written manually as that's how you control the test. Commented Sep 20, 2017 at 10:43
  • Which bit exactly are you looking to improve? Do you essentially want different data providers for each constructor argument? Commented Sep 20, 2017 at 10:44
  • As an aside, if you're on PHP 5.6, you can tidy up the test method itself using the splat operator in both the declaration and the call to the constructor, to save listing out the arguments in full. Commented Sep 20, 2017 at 10:44
  • @iainn The different arguments are validated by different validation methods. To test them though I am providing all arguments, but with one argument value being invalid which seems a little verbose. Wondering if I can test the private validation methods without having to provide all the other arguments? Commented Sep 20, 2017 at 11:03
  • I've added a better example of the way I am currently structuring my dataProvider for providing valid/invalid argument values for testing. Commented Sep 20, 2017 at 11:08

1 Answer 1

2

I agree with your comment that having validation logic in your value object is a matter of taste, but one major drawback is that it does make unit-testing harder, as you're seeing. If your value object is now responsible for two things (data storage and validation), then testing gets more complicated, especially if you've made the validation logic private.

One approach you might want to consider is to use Reflection to test your private methods directly. You'll find a lot of debate about whether this is bad practice in some of the related questions linked to the side of this one, which I won't go over again here. For my own point of view, I think this is one of the few cases where it makes sense.

You can use something like this to run a private method from the unit test directly:

/**
 * @test
 * @dataProvider invalidAddressLine1Provider
 */
public function invalid_parameter_throws_exception($invalidParameter)
{
    $reflector = new \ReflectionClass(Foo::class);
    // The call to newInstanceWithoutConstructor here is important, since the
    // constructor is what we're looking to avoid
    $instance = $reflector->newInstanceWithoutConstructor();

    $method = $reflector->getMethod('validateAddressLine1');
    $method->setAccessible(true);

    $this->expectException(\Exception::class);
    $method->invoke($instance, $invalidParameter);
}

You could also combine the invalid parameters for all validation methods into a single dataProvider, and pass the method name in as an argument, to save duplicating the Reflection code.

public function invalidProvider()
{
    return [
        ['validateAddressLine1', 'invalid value for line 1'],
        ['validateAddressLine2', 'invalid value for line 2'],
        // ...
    ];
}
Sign up to request clarification or add additional context in comments.

1 Comment

I have considered this and may do so so that I can test the methods independently of the constructor and the other methods. There is a good article on testing private methods with PHPUnit here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.