18
votes

I am trying to test methods from the following class I have written (there are more functions than what is shown, basically, one function for each is_*() method):

class Validate {
  private static $initialized = false;

  /**
  * Construct won't be called inside this class and is uncallable from the outside. This prevents
  * instantiating this class. This is by purpose, because we want a static class.
  */
  private function __construct() {}

  /**
  * If needed, allows the class to initialize itself
  */
  private static function initialize()
  {
    if(self::$initialized) {
      return;
    } else {
      self::$initialized = true;
      //Set any other class static variables here
    }
  }

  ...

  public static function isString($string) {
    self::initialize();
    if(!is_string($string)) throw new InvalidArgumentException('Expected a string but found ' . gettype($string));
  }

  ...

}

When I test if the methods throw an exception on invalid input, it works great! However, when I test if the method works as expected, PHPUnit complains because I have no assert in the test. The specific error is:

# RISKY This test did not perform any assertions

However, I don't have any value to assert against so I'm not sure how to overcome this.

I've read some about testing static methods, but that mostly seems to cover dependencies between static methods. Further, even non-static methods could have no return value, so, how to fix this?

For reference, my test code:

class ValidateTest extends PHPUnit_Framework_TestCase {
  /**
  * @covers ../data/objects/Validate::isString
  * @expectedException InvalidArgumentException
  */
  public function testIsStringThrowsExceptionArgumentInvalid() {
    Validate::isString(NULL);
  }

  /**
  * @covers ../data/objects/Validate::isString
  */
  public function testIsStringNoExceptionArgumentValid() {
    Validate::isString("I am a string.");
  }
}
3
Why would that method not return true/false? WHy would it be an exception when a non-string value is passed as parameter, as obviously a method to test if something is a string, should expect non-string values or there is not utility to the method. This seems like an ill-conceived method.Mike Brant
@MikeBrant It doesn't return because it has no need to. I use the method in my code as a break/assert - if the method doesn't throw an exception my code continues on as normal. If it does, the exception must be dealt with. There is no reason at all to return anything from these methods, and even less reason to write if/else statements and spend cycles checking those return values. I want exception instead of TRUE/FALSE because exceptions can stop code execution. If you put an int where an array should go in Java (or most typed languages) you get an exception. I want similar functionality.Matthew Herbst
@MikeBrant Further, proper testing means ensuring my method works for both invalid and valid input.Matthew Herbst
Yes, but when you have a validation method like this, why should the validation class dictate behavior to the caller? Maybe the code calling this class is trying to make a decision on something to do based on whether a value is a string, fully expecting that sometimes that other data types may be present. Why should this validation class throw an exception, which must than be handled, rather than just giving the caller what it is looking for - an understanding of whether the value in question is indeed of string type? This also seems like a trivial function in that PHP has this built-in.Mike Brant
@MikeBrant PHP does have it built it, is_string(), I use it in the function. However, since I do use exceptions, I don't want to be writing code for new exceptions all over the place. Having a validation class is not uncommon. I understand what you are saying about how the class works, and you have valid points. However, the way you point out is not the way I designed it, and while my design might not be perfect in general, it's the way I think it should be for what I am doing.Matthew Herbst

3 Answers

8
votes

To prevent the warning about the assertions you can use the @doesNotPerformAssertions annotation as explained in the documentation: https://phpunit.de/manual/current/en/appendixes.annotations.html#idp1585440

Or if you prefer code over annotation: $this->doesNotPerformAssertions();

6
votes

Test void function with assertNull:

    /**
     * @covers ../data/objects/Validate::isString
     */
    public function testIsStringNoExceptionArgumentValid() {
         $this->assertNull( Validate::isString("I am a string.") );
    }
4
votes

One solution I have come upon is the following, based on example 2.12 from chapter 2 of PHPUnit. It feels a little hacky to me, but it's the best I've found so far. Also, based on this PHPUnit Gitub issue discussion, it seems several other people want this feature but that there are no plans to implement it.

Change testIsStringNoExceptionArgumentValid() to the following:

  /**
  * @covers ../data/objects/Validate::isString
  */
  public function testIsStringNoExceptionArgumentValid() {
    try {
      Validate::isString("I am a string.");
    } catch (InvalidArgumentException $notExpected) {
      $this->fail();
    }

    $this->assertTrue(TRUE);
  }