Search code examples
phpunit-testingphpunitcode-coverage

How to unit test something like this?


I have an example like this which is based on the Laravel key generator:

public function generateRandomKey(): string
{
    $generatedKey = base64_encode(random_bytes(16));

    // Encrypt the generated key with our public key so it is not a 'plain password' anymore.
    $value = openssl_public_encrypt($generatedKey, $crypted, $this->getPublicKey());

    if (!$value) {
        throw new \RuntimeException('Encryption failed: could not generate a random key.');
    }

    return base64_encode($crypted);
}

I would like to unit tests this and I can, expect for my RuntimeException. I would like to have 100% code coverage but I also don't want to force scenario's just to get 100% code coverage.

In this example I would would to have PHPUnit hit the exception as well. I can not provide a wrong key because my getPublicKey() is private and will throw an error before I get to the error in this method.

Triggering decryption errors is not that hard since I can just supply a random value that has not been properly encrypted.

So how would I be able to test a scenario like this and achieve 100% code coverage. Is this possible or even wise to test something like this or should I rather ignore it with a PHPUnit comment or something?

Cheers.


Solution

  • If 100% coverage is your goal then you will need to proceed down the route of potentially "over optimising" your code for the sake of it. In this case it wouldn't be too much of a hit though.

    One option is to abstract out the encryption line into its own partially mockable method, however by doing so (and telling PHPUnit what it will return) you're essentially just checking that an exception can be thrown (literally).

    Semantically speaking, getter/setter methods would tend to be public - while that's not strictly enforced by any body. I can entirely see why you wouldn't want getPrivateKey as part of your API, but you could feasibly add a setPrivateKey method to your public API - this would solve your unit testing issue:

    # File: YourClass
    public function setPrivateKey(string $key) : YourClass
    {
        $this->privateKey = $key;
        return $this;
    }
    

    Then:

    # File: YourClassTest
    /**
     * @expectedException RuntimeException
     * @expectedExceptionMessage Encryption failed: could not generate a random key.
     */
    public function testGenerateRandomKeyThrowsExceptionWhenCannotEncrypt()
    {
        $class = new YourClass;
        $class->setPrivateKey('highly-unlikely-to-be-a-valid-private-key');
        $class->generateRandomKey();
    }
    

    Of course - by doing this you run into the argument that "you should not test code you don't own", i.e. the openssl method. This is a trade-off that you'll make to achieve 100% coverage if that's your goal.