Running specific tests using Gulp TDD in Laravel Elixir

Today my team was trying to refactor some code and we wanted phpUnit to continually tell us if anything was breaking. Jeffrey Way’s excellent Elixir package that is included with Laravel now includes the option to run gulp tdd which watches the tests being edited and runs the tests once the files are saved. In our case today, however, I didn’t want it to run all of the tests since we have a fairly significant test suite. I’ll quickly go through a few ways this can be done!

To be clear, below is all that it takes to get Laravel Elixir up and running using phpUnit. Once you have added this mix.phpUnit(); to your gulpfile.js, all you need to do is run gulp tdd from your command line and you are off to the races!

elixir(function(mix) {
    mix.sass('app.scss');
    mix.phpUnit();
});

After looking through the gulp-phpunit API and the Elixir implementation, I realized there were two options that could be passed into mix.phpunit(). They are mix.phpunit([src], [options]).

[src] specifies where phpUnit should look for the source files to run against. In my case, I have a set of directories inside my tests folder so my value for src needed to be ‘/tests/**/*Test.php’. This will run inside one level of subdirectories with any file that ends with Test.php.

[options] is where you can go all kinds of crazy passing things into phpUnit. All you need to do is pass in an object with whatever settings you would like to use. Many other things can be passed in other than just a specific class. For the entire listing of options that can be passed in, go to https://github.com/mikeerickson/gulp-phpunit#api and check out the API documentation for the gulp-phpunit package.

So what did the entire thing look like once it was completed?

mix.phpUnit(['tests/**/*Test.php'], {
    testClass: 'tests/TestSubdir/SpecificTaskTest.php'
});

Another way this could be done is by using the filter option which allows a pattern to be passed instead of a specific file. Say I have 3 files in my TestSubdir that I want to run and they are named SpecificTaskTest.php, SomeOtherTest.php, and AnotherTaskTest.php. I could specify a pattern of *TaskTest.php and it would run the two tests that end in TaskTest.php. The way this would be used is as follows:

mix.phpUnit(['tests/**/*Test.php'], {
    filter: 'tests/TestSubdir/*TaskTest.php'
});

Check out the latest documentation for additional filter examples if desired: https://phpunit.de/manual/current/en/textui.html#textui.examples.filter-patterns. You can also use a testSuite or a group to specify the individual tests that should run.

One of the other interesting options was configurationFile. With this, you can actually specify a different phpunit.xml file to be used when running gulp tdd. I could see this being useful in instances where your tdd workflow looks a bit different from when you run your entire test suite (perhaps with code coverage or other more time-consuming tasks that you may not want to run when performing quick tdd/refactoring).

Testing Private Methods in PHPUnit

A few days back I was running a code coverage report in PHPUnit. If you haven’t used a code coverage report before, it basically shows the codebase, and identifies locations where your code is covered by tests and areas that are not ever hit when running your test suite.

As a result of running this report, I realized there were a few critical holes in a particular method. I had a transformation method on one of my api gateways that Highlighting was parsing the data being returned and then presenting my application with a consistent result that I could count on. Some of the cases that could happen, however, were not covered in my test suite.

Since the third-party API Erfurt endpoint I was hitting didn’t allow me to specify the data I could possibly expect to get back, I wanted to be sure the code I had written to standardize the results was consistent with how I thought it would run. In this instance, I wanted to actually test how specific use cases would be handled, but one of the methods being called was a private method and I wanted to test it individually.

My solution to solving this problem was found in the ReflectionClass in PHP. The reflection class essentially reports information about a given class (for more information, check out php.net’s detailed information. I created a method called invokeMethod() that wholesale nba jerseys allows den us to essentially run a private method individually.

public function invokeMethod(&$object, $methodName, array $parameters = array())
{
    $reflection = new \ReflectionClass(get_class($object));
    $method = $reflection->getMethod($methodName);
    $method->setAccessible(true);
    return $method->invokeArgs($object, $parameters);
}

This method is called by creating a new instance of a class, and then passing it & in:

$myClass = new \App\CustomClasses\MyClass();
$state = $this->invokeMethod($myClass, 'retrieveState', array('Madison, WI'));
$this->assertEquals('WI', $state);

The API just returns data from a claim system that will sometimes include a City/State, just cheap jerseys a State, or a City/State/Zip and what I specifically need is the state abbreviation. This allowed me to effectively call $this->retrieveState(); even though it is a private method and ensure what I expect to get back in many instances is always correct. Important? By being able to call this private method, I was able to test things like:

$myClass = new \App\CustomClasses\MyClass();
$state = $this->invokeMethod($myClass,  command  'retrieveState', array('Wisconsin'));
$this->assertEquals('WI', $state);

and

$myClass = new \App\CustomClasses\MyClass();
$state = $this->invokeMethod($myClass, 'retrieveState',  Legislative  array('Madison, WI 12345'));
$this->assertEquals('WI', $state);

I understand there is also a possibility of being able to mock the response and then perform tests that way, wholesale nfl jerseys but this felt very clean and it was extremely clear to the other developers who were also looking at the code. Sound off in the comments below if you have other thoughts and I can update this post as appropriate.