Be More Asssertive: Getting to Know PHPUnit’s Assertions+

    Chris Cornutt
    Share

    In my previous article, I took you through some of the basics of unit testing with PHPUnit. I introduced you to what it means to test your code and the real benefits it can for you and your code. This time I want to dive in a little deeper and get to know PHPUnit in a bit more detail.

    Marking Tests Skipped or Incomplete

    PHPUnit includes two methods that, if used correctly, can make your testing life a little simpler. The two methods, markTestSkipped and markTestIncomplete, allow your tests to have different results than just passing or failing. markTestSkipped gives you a bit of an “out” if a problem is encountered in the course of running your tests.

    Say you’re testing to see if a database record exists after the execution of a given method. In a perfect world, you’d always be able to connect to the database. What happens if you can’t, though? Well, your test would most certainly fail, since it couldn’t find the row. But that failing test might lead you to believe there was a problem with your code, even though there might not be. With the help of markTestSkipped, you can simply skip over that particular test in the current test run. Here’s an example:

    
    <?php
    
    public function testThisMightHaveADb()
    {
      $myObject->createObject();
    
      try {
        $db = new Database();
        $this->assertTrue($db->rowExists());
      } catch (DatabseException $e) {
        $this->markTestSkipped('This test was skipped because there was a database problem');
      }
    }
    ?>

    There are three possible outcomes here. First, of course, if everything works as intended, the test will pass. It’s also possible that the assertTrue could fail, if the rowExists method returns false.

    But finally, and most interestingly, the database object could throw a DatabaseException type of exception. Our try/catch block will catch the exception, which indicates that the database is unavailable, and therefore that the whole point of the test is moot. Instead of failing, it just skips the test. The “skipped” result will still show up in all of your testing runs, so you’ll still be made aware that the issue will need to be resolved eventually.

    The markTestIncomplete method provides us with similar functionality, but with a different twist. It’s intended to give you a way to mark a test as, big surprise, incomplete. What this means is that if you begin writing a test, and intend to come back to it later to finish it, you can make it clear in the test results that it’s not a failure of the application code, but rather an incomplete test. You can even define a custom message so you’ll know why a test was skipped:

    <?php
    
    public function testAreNotEnoughHours()
    {
      $this->markTestIncomplete("There aren't enough hours in the day to have my tests go green");
      $trueVariable = true;
      $this->assertTrue($trueVariable);
    }
    ?>

    It’s important to note that these two methods are a convenience only, and shouldn’t be an excuse for you to write sloppy tests. Your application is only as good as the tests that support it, so be sure to revisit any incomplete or skipped tests once you’re done working on the primary functionality you’re testing.

    Assertions, Assertions, Assertions

    In the last article, I reviewed a number of often-used assertions provided by PHPUnit. Now, we’ll review them and put them into practice in some sample code to show you how they’re used.

    Let’s start out with the sample class we’ll be testing:

    <?php
    
    class Testable
    {
      public $trueProperty = true;
      public $resetMe = true;
    
      public $testArray = array(
        'first key' => 1,
        'second key' => 2
      );
    
      private $testString = "I do love me some strings";
    
      public function __construct()
      {
      }
    
      public function addValues($valueOne,$valueTwo) {
        return $valueOne+$valueTwo;
      }
    
      public function getTestString()
      {
        return $this->testString;
      }
    }
    
    ?>

    Let’s also set up our test class. This will be the container we drop the rest of the tests into so they can run against our Testable class:

    
    <?php
    
    class TestableTest extends PHPUnit_Framework_TestCase
    {
      private $_testable = null;
    
      public function setUp()
      {
        $this->_testable = new Testable();
      }
    
      public function tearDown()
      {
        $this->_testable = null;
      }
    
      /** test methods will go here */
    }
    
    ?>

    We’re using the setUp and tearDown methods to prepare for our test run. I mentioned those methods briefly in the last article, but now it’s time to put them to use. The setUp method will be run before all of the tests in the test class, and tearDown will be run after all the test methods. In this case, we’re just creating a new instance of our Testable class and storing it so we can easily access it in each test method.

    True or False

    Now that we’ve got an instance of our class to test, let’s get into our assertions. PHPUnit makes it super simple to test your application one little bit at a time. Remember, the point of unit tests is to test “units” of code, not the whole flow of the application. If you’re tracing paths through your application, you’re probably doing it wrong. There are, of course, exceptions to the rule, but it’s a handy one to follow. Let’s get started with some of the simplest assertions—assertTrue and assertFalse.

    <?php
    
    public function testTruePropertyIsTrue()
    {
      $this->assertTrue($this->_testable->trueProperty,"trueProperty isn't true");
    }
    
    public function testTruePropertyIsFalse()
    {
      $this->assertFalse($this->_testable->trueProperty, "trueProperty isn't false");
    }
    
    ?>

    We looked at assertTrue and assertFalse in the last article, but we’ve added a twist here. See that handy message as the second parameter each assertion? This lets you define a custom message to be output if the test fails, rather than the default PHPUnit output, which can sometimes be a little cryptic. “Failed asserting that is false” won’t help you understand what’s wrong with your application code, but a message like “could not create user” is much more helpful!

    Mathemagic

    Next up we’ll look at some of the more mathematically oriented assertions. These let you evaluate the variables passed in to see how they relate. Let’s throw them all into one big test for the sake of illustration:

    <?php
    
    public function testValueEquals()
    {
      $valueOne = 4;
      $valueTwo = 2;
      $this->assertEquals($this->_testable->addValues($valueOne,$valueTwo),6);
    }
    public function testValueGreaterThan()
    {
      $valueOne = 4;
      $valueTwo = 2;
      $this->assertGreaterThan($valueTwo,$valueOne);
    }
    public function testLessThanOrEqual()
    {
      $valueOne = 4;
      $valueTwo = 2;
      $this->assertLessThanOrEqual($valueTwo,$valueOne);
    }
    public function testAreObjectsEqual()
    {
      $testTwo = new Testable();
      $this->_testable->resetMe = false;
      $this->assertEquals($this->_testable,$testTwo);
    }
    
    ?>

    Thanks to the clear naming of the methods, most of the code above is fairly self-explanatory: you have access to methods such as assertGreaterThan, assertLessThan, assertGreaterThanOrEqual, assertLessThanOrEqual, and assertEquals.

    The first assertEquals call demonstrates how you can use a method from the class your testing to provide the values to test against.

    The second assertEquals call demonstrates a very interesting use of this assertion. Not only can it be used to compare numeric values, but it can compare other things too—like objects. See that testTwo object we created in the test? We’ve changed the resetMe property to false instead of true. The assertEquals takes a look at the objects as a whole to see if they match. Because of the changed resetMe value, these two objects don’t match, so the assertion fails. assertEquals also has a few other tricks up its sleeve: it can compare DOMDocument objects or arrays. For more details on how these comparisons work, check out the relevant section in the PHPUnit documentation.

    Stringing You Along

    In addition to the math-related methods PHPUnit comes with, there are also a few that are specifically for working with strings. Keep in mind, of course, that PHP itself comes with a wide array of string handling methods, all of which you can use in your tests. These PHPUnit assertions are just there to make writing your tests a little easier and cleaner. Let’s have a look at a few of them:

    <?php
    public function testStringEnding()
    {
      $testString = $this->_testable->getTestString();
      $this->assertStringEndsWith('frood',$testString);
    }
    
    public function testStringStarts()
    {
      $testString = $this->_testable->getTestString();
      $this->assertStringStartsWith('hoopy',$testString);
    }
    
    public function testEqualFileContents()
    {
      $this->assertStringEqualsFile('/path/to/textfile.txt','foo');
    }
    
    public function testDoesStringMatchFormat()
    {
      $testString = $this->_testable->getTestString();
      $this->assertStringMatchesFormat('%s',$testString);
    }
    
    public function testDoesStringFileFormat()
    {
      $testString = $this->_testable->getTestString();
      $this->assertStringMatchesFormatFile('/path/to/textfie.txt','foo');
    }
    
    ?>

    As with the math methods, the naming of these assertions gives you a clear indication of what they do. The first and second methods look at the string and see if it either starts with or ends with the given string. assertStringEqualsFile is a little more interesting: it examines the file you give it and tests to see if its contents are equal to a given string. This saves you the hassle of having to call file_get_contents to grab the data and check it yourself. In the above example, the test would pass if you had a file in the given path containing the string "foo".

    The next two methods, assertStringMatchesFormat and assertStringMatchesFormatFile, allow you to be a little more fine-grained in your matching technique. They let you define patterns to match against, using a set of placeholders you can find on the PHPUnit site. This way, you can verify if your values conform to a given format, which you can either provide as a string or as a file.

    More of the Same?

    Now let’s look at two other handy assertions that seem to constantly come in handy for me in my testing: assertNull and assertSame. They both do pretty much what their names describe, but here’s an example to cement the idea in your head.

    <?php
    
    public function testStringIsNotNull()
    {
      $notANull = “i'm not a null!”;
      $this->assertNull($notANull);
    }
    public function testStringIsSame()
    {
      $numberAsString = '1234';
      $this->assertSame(1234,$numberAsString);
    }
    
    ?>

    The first assertion in our above test is going to fail. It’s checking to see if the value in $notANull is NULL, but it’s a string. The advantage to assertNull is that in cases like this it will be more clear and readable than trying to accomplish the same check with assertTrue or assertFalse.

    Now for assertSame. As you probably know, PHP is loosely typed, so it will consider the number 1234 and the string "1234" to be equal. As a result, assertEquals(1234, "1234") will pass. However, being a conscientious coder, you’re being very careful with your data types and want to ensure that a given variable exactly matches another, both in contents and in type. Well, you’re in luck, because that’s exactly what assertSame does. Because of this, the assertion in the example above will fail, as 1234 isn’t the same as "1234".

    A Mixed Bag

    I’m going to touch on a few of the less common assertions available in PHPUnit. While you probably won’t use them in every test you write, they can be tremendously handy when you do need them:

    <?php
    
    public function testArrayKeyExists()
    {
    	$this->assertArrayHasKey('first key',$this->_testable->testArray);
    }
    public function testAttributeExists()
    {
    	$this->assertClassHasAttribute('resetMe',get_class($this->_testable));
    }
    public function testFileIsReal()
    {
    	$this->assertFileExists('/path/to/file.txt');
    }
    public function testIsInstance()
    {
    	$this->assertInstanceOf('OtherClass',$this->_testable);
    }
    
    ?>

    I’ve introduced four new assertions here covering a wide range of features—array keys, class atributes, file existence, and object type. Let’s take them one at a time and get a feel for what they do. The first assertion simply checks the array from our sample class to see if "first key" is a valid array key for it. We’ve given our Testable class an array with that key, so this test passes with flying colors. Next, thanks to the assertClassHasAttribute method, we can check to see if our class has a given property. Again, this assertion happily passes because our test class has the resetMe property. Take note, though, that this is not to check if a property exists on a given object, only to see if its defined in a class.

    assertFileExists is another easy one—it just looks on the local file system to see if the file’s there. This follows the same rules as the PHP file system methods—if you can’t access it, the assertion will fail. Lastly, there’s assertInstanceOf, a shortcut method to tell if the object you’re working with is an instance of a given class. Of course, this is no different from assertTrue($this->_testable instanceof OtherClass), but it’s more concise and easier to read.

    For the final assertion in this article, we’ll look at one of the most flexible:

    <?php
    
    public function testDoesMatchRegex()
    {
      $testString = $this->_testable->getTestString();
      $this->assertRegExp('/[a-z]+/',$testString);
    }
    
    ?>

    This is a simple example (I’m sure you’ve seen some monster regular expressions in your time) but it gets the point across.

    In Conclusion

    I’ve tried to list out most of the more useful assertions in this second article so you can have a better idea of what’s out there before you start testing. There’s always more than one way to do things, but these built-in assertions can save you a lot of time and hassle. Remember, the point to writing tests for your application isn’t just to say that you have, or to reach 100% code coverage. Writing good tests allows you to work on your code free of the fear that you might introduce new bugs (or reintroduce old ones). Ultimately, better tests will make for better code.