Try Tuts+ Premium, Get Cash Back!
Hands-On Unit Testing With PHPUnit

Hands-On Unit Testing With PHPUnit

Tutorial Details
  • Difficulty: Intermediate
  • Completion Time: 45 Minutes
This entry is part 8 of 12 in the Test-Driven PHP Session
« PreviousNext »

In this screencast, we’ll build a relatively simple class, using TDD techniques. Along the way, we’ll discuss various PHPUnit methods, how to create mock objects, and more! I encourage you to work along; it’s the best way to learn!

Please choose 720p for optimal viewing.

Closing Thoughts

As with anything, there are various ways to approach test-driven development. Like they say, it doesn’t matter as much how you test, as long as you are testing. If you have a different approach, let us know in the comments. I’d love to learn more!

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • eric

    Great overview Jeffrey. You have become quite a guru!

  • http://www.xcellence-it.com/ Xcellence-IT

    Great hands-on, very important & useful to developers who never tried this. Thanks!

  • http://dextructables.com Judas Borbon

    Love these TDD tutorials. I’ve read DataBase mocking is one of the most tedious and complicated kind of testing but can we expect a small example of that? But still, very cool tutorials. Unit Testing is something I want to incorporate to my workflow and with tools like Travis.ci it gets much more fun and practical.

  • Daniel

    Awesome tutorial, Jeffrey! Now also the one on dependency injection and not hard coding classes makes a lot more sense.

  • http://abderrahmane-tj.co.cc Abderrahmane TAHRI JOUTI

    Hey Jeffrey, nice tutorial again. I went trough the Sublime Text Tutorials, and did not find a tool to make it have those fancy autocomplete menus you have. any lead ? thanks

    • Matt

      Same question – how did you get Sublime to do that cool auto-completion for PHP methods?

      Thanks for the great tutorial!

    • Dennis

      I’m interested in this as well – anyone, any ideas?

    • Rogier Borst

      With Package Control for Sublime Text, install the package “PHPUnit Completions”.
      Voilá, instant autocomplete menus for PHPUnit.

  • Ian

    Thank you for the great overview. Being into Laravel right now, it was good for me that you showed it in Laravel but I can see how the same applies when not working with a framework.

    This was a good easy to understand example of using a mock object.

  • http://baylorrae.com Baylor Rae’

    I’ve put my comment on GitHub because it’s kind of long and I can never get code blocks to work. I’ve included the text below, but it it doesn’t look right please view it here. https://gist.github.com/65a049ad1d049fbe8070

    This is an awesome tutorial on writing tests for a PHP framework. Most articles on Test Driven Development in PHP focus on a couple of standalone files and I was very excited to see that you worked with an actual, production ready PHP framework.

    However, there are a few things that I would like to point out. I’m by no means an expert in TDD and if you disagree with anything I say please let me know in a reply. After watching the video there were a few things that I thought should be changed to improve the tests and decided to add them as a comment.

    ## Extract the tests from a real environment

    What I mean is create your own environment that you have complete control of. In your tests you were working with real data that was irrelavent to the tests itself. Instead you should have used your own data that was independant of the class.

    Near the end you extracted and stubbed the `Asset::fetch()` method, but I don’t think that was the best solution. Here are a couple of alternatives, the second being my personal choice.

    1. In `Fetch_Test::setUp()` assign `$this->fetch` to a stubbed version of `Fetch`.
    2. Modify `Fetch_Task::$paths` to use local files.

    By using local files you can still use `file_get_contents()` without running into issues. But, in addition you force your class to use the environment you create.

    Here’s as example. The following will load “jQuery” from a local file. Because `Fetch_Task` isn’t parsing the file, you can put whatever content you want in it.

    public function setUp() {
    Fetch_Task::$paths = array(
    ‘jquery’ => dirname(__FILE__) . ‘/fetch-factories/jquery.js’ // create this file with some fake content
    );

    // Fetch_Task now uses the local file for jquery
    $this->fetch = new Fetch_Task;
    }

    Here are the reasons “stubbing” the source list is better.

    1. You don’t need to add a second class or stub the method.
    2. It still works with real files.
    3. You can test the content in `public/vendors/js` and make sure it’s correct.

    ## Don’t put content from test files in the working application

    Something I noticed that almost made me faint was watching you add the following lines to your `tearDown()` method.

    public function tearDown() {
    File::cleandir(static::$cssDir);
    File::cleandir(static::$jsDir);
    }

    I realize that these days if you’re not using a SCM then “you’re probably doing something wrong”. But, I pitty the person, I won’t call them a fool :), that runs the tests and accidentally loses all the vendor assets they’ve downloaded.

    Here’s how I would work with temporary files, put them in a `tmp` directory. I’m not familiar with Laravel and there may be a better place do something like this, but you get the point.

    public function setUp() {
    // there may be a better place to put the files
    Fetch_Task::$cssDir = ‘storage/tmp/fetch_task_tests/css’;
    Fetch_Task::$jsDir = ‘storage/tmp/fetch_task_tests/js’;
    // …
    }

    public function tearDown() {
    File::cleandir(Fetch_Task::$cssDir);
    File::cleandir(Fetch_Task::$jsDir);
    }

    I know this is a tutorial and the code in tutorials don’t need to be perfect. But I wanted to share some of the things that I’ve learned that other’s may find useful. As I mentioned earlier, if you disagree with something I said please let me know in a reply. I love criticism; fire away!

    • http://www.jeffrey-way.com Jeffrey Way
      Author

      Hey Baylor –

      You’ll often see frameworks abstract things like file_get_contents out to their own utility class, such as File. We could have stubbed $this->fetch in this lesson, but I chose not to.

      Cleaning the CSS and JS folders is clearly meant to be used in a test-only project. These tests aren’t intended to be run in a real-life project. But, sure, if you wanted to, it’s better to use a temp dir.

  • http://dari.us.lt Darius

    I like the way you take advantage of sublime text editing in multiple places at the same time. This function for me is annoying because I accidendally edit multiple places where I don’t have, but you use it for what it is probably made :) And code writing speed is just sick there :)

  • Colyn

    For your tests, I would change the write path so that they don’t use the same as your application. Your teardown deletes everything and that’s probably not what you want.

  • Sergey

    Jeffrey, I just wonder – why did you save asset name in $this->asset and not in local variable $asset if you’re passing it as an argument everywhere? ..Is it because it is more efficient to create object variable or because you could benefit from it somehow in future? Please advise.

  • http://www.willianveiga.com Willian Gustavo Veiga

    I would use vfsStream (https://github.com/mikey179/vfsStream) to mock the file system. As you said, tests must run fast, if they don’t, them you’ll skip them and bad things will happen :)

    I don’t like to wrap functions. I like to “cheat” using that namespace trick (http://pastebin.com/V24LZyUW). I would create a file_get_contents function in the same namespace of the test. This way, this function would be called, not the native one. We don’t have a class dependency to handle.

    By the way, when you inject the Asset class you still hardcoded Asset in __construct. With a mock, you just pass it through the method and it will act like the real class. You don’t need that if/else statements at all!

    As Baylor Rae, I’m not a TDD guru too. I might be wrong (https://www.youtube.com/watch?v=kmj176eFXEI). Please, forget about my bad english :)

    Great tutorial, please, don’t stop to write more about these kind of subjects!

    • http://baylorrae.com Baylor Rae’

      I haven’t seen vfsStream before, it’s definitely something I’m going to look at. Thanks for sharing the link.

      I’ve never actually used monkey patching and I have a question. When you add a monkey patch in one file, does it carry over to another? For instance, if you redefine file_get_contents() in fetch_task_test.php does fetch_task.php use the modified version?

  • http://anorgan.com Marin

    Nice tutorial. I would just like to point out that it would be better if you use vfsStream (as pointed out), but not only for speed! Your tests would pass even if code is incorrect if you have jquery.js in your vendors directory PRIOR to running the test. Code would do nothing, and expected file exists would say “Yes, file’s there, it’s A-OK!”

    Additionally, in the test you said that the name of the file should be jquery.js, and in the code you say $file['basename'], and asset named jquery might be changed to something like jquery-min.js, or jquery-2.0.0.js or something. That way, your code would run ok, but the tests would fail.

  • Kate

    Thank you for this great tutorial Jeffrey! We were happy to include this article into a digest of the most interesting PHP news and updates: http://www.zfort.com/blog/php-digest-september-29-zfort-group/

  • http://prointernetmarketing.co.uk Chris

    Brilliant, well presented and very clear. Something more and more jobs expect so this was on my to-do list to learn about. It also helps think about your classes from an almost inverse perspective. Writing to pass tests so making you think more about your code. Great stuff.

  • ssy

    How did you setup Laravel so that you can execute PHPUnit by just typing phpunit and not php artisan test? Did you make an alias? It wasn’t covered in the video.

    • http://www.jeffrey-way.com Jeffrey Way
      Author

      Should be automatic.

  • Darius

    where the old comments had gone?

    • JeffWay
      Author

      They will be back soon. We’re just migrating over to Disqus.

  • me

    Youtube is blocked here. Would appreciate if another video link is provided.

  • M G

    Give your coworkers a laugh by adding this into one of the tests;

    echo “ISSSFFSSSISS…SISI..SIIS.FFF….F.F.F.F.F.F”;

    they will :head-desk: trying to fix the borken tests :P

  • Yo

    My problem here is Laravel. It doesn’t recognize the aliases of Laravel and PHPUnit is throwing fatal error: Class ‘File’ nto found in path/to/laravel/application/tests/fetch_test.php

  • john