I am using Laravel 5.3 and unfortunately when you run gulp tdd
, a change to 1 file runs the entire test suite which now takes nearly 2 minutes. With reference to this post, I started using Grunt to run specific tests when specific files are changed. Sample Gruntfile below:
Gruntfile.js:
var phpunit = 'vendor/bin/phpunit ';
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
watch: {
HomeSrc: {
files: [
'app/Http/**/HomeController.php',
'resources/views/home/**/*.php'
],
tasks: ['HomeTests']
},
shell: {
HomeTests: { command: phpunit + 'tests/Home' },
}
});
However, now my Gruntfile is getting pretty long and I would like to run specific test files when they are changed.
Questions
- Is there a more efficient way to do this? (better organization of the Grunfile or using Gulp instead)
- How can I run a specific test when its file is changed?
Example:
When tests/Home/IndexTest.php
is changed, automatically run vendor/bin/phpunit tests/Home/IndexTest.php
OK, to handle this you will need to catch the matched file name and dynamically set a variable to use as the unit test file. This should cover all basic mappings where the test class name is exactly the same name as the file name it is within, and supports namespaces so not all test files with the same class name will be picked up by the filter.
Example:
grunt.initConfig({
// .. snipped ..
unitTestFile: 'to_be_replaced',
watch: {
php: {
files: ["tests/**/*.php"],
tasks: ["shell:unitTest"],
options: {
spawn: false
}
}
},
shell: {
unitTest: {
command: "phpunit --filter <%= unitTestFile %>"
}
}
grunt.loadNpmTasks('grunt-shell');
grunt.event.on('watch', function (action, filepath) {
if (grunt.file.isMatch(grunt.config('watch.php.files'), filepath)) {
var testFile = filepath.replace(/\\/g, '\\\\');
grunt.config('unitTestFile', testFile.replace(/.php/, ''));
}
});
};
So, a file named tests\unit\ApplicationTest.php
and within a namespace of tests\unit
if changed will now run that as a test. The resulting command being:
phpunit --filter tests\\unit\\ApplicationTest // only runs in this namespace
Using command line
go to your project folder then run below suitable command
Run all test class files
phpunit
Run specific test class file
phpunit ./tests/ExampleTest.php
or
Run specific test case from class file run below command
phpunit --filter testBasicExample ./tests/ExampleTest.php
You could consider the following solution:
- Utilize an external
.json
which includes an array of files/paths you intend to watch and ultimately run as a unit test.
- Dynamically generate the Targets for both the
watch
and shell
Tasks.
(Both points above will certainly help to reduce the lines of code.)
The following gist demonstrates this:
JSON
Lets assume we have a file named test-mappings.json
with each file path (as per point 1) specified as followed and saved in the projects root directory alongside Gruntfile.js
[{
"file": "tests/testA.php"
},{
"file": "tests/testB.php"
},{
"file": "tests/testC.php"
},{
"file": "tests/testD.php"
}]
Gruntfile.js
Use a Gruntfile.js
configured as follows:
module.exports = function(grunt) {
'use strict';
var mapping = grunt.file.readJSON('test-mappings.json'),
watch = {},
shell = {};
// Dynamically create the targets for 'watch' and 'shell' tasks.
mapping.forEach(function(config, index) {
watch[index] = {
files: [config.file],
tasks: [index]
};
shell[index] = {
command: 'vendor/bin/phpunit ' + config.file
};
// Register the shell target
grunt.registerTask(index, ['shell:' + index ]);
});
grunt.initConfig({
watch: watch,
shell: shell
});
// Handy for dev - logs generated targets
//grunt.log.writeln(JSON.stringify(grunt.config(), null, 2));
require('load-grunt-tasks')(grunt);
grunt.registerTask('default', [
'watch'
]);
};
Notice each Target for both the watch
and shell
Tasks are dynamically generated, and configured, according to the file list specified in test-mappings.json
.
To see the configuration of the auto generated targets you can simply uncomment the line reading grunt.log.writeln...
, before running $ grunt
, and it will be printed to the console.
Running the task
- Type
$ grunt
via the CLI.
- Make an edit to one of the files listed in
test-mappings.json
and save it.
- The
watch
task will then run the corresponding shell
command.
Additional notes
Currently the files are listed in an external .json
, however they could reside in the Gruntfile.js
and be assigned to a variable if preferred.
Or, the files could be obtained using the appropriate globbing pattern(s) and modifying the gist above as necessary. In which case iterating over grunt.file.expand will be handy for that, instead of the current mapping
array.
The following grunt plugins were used for the above gist:
grunt-contrib-watch
grunt-shell
load-grunt-tasks
Update:
The following Gruntfile.js
is for the approach previously mentioned in point no. two under the Additional notes secton (i.e. Globbing instead of an external JSON filepath config).
module.exports = function(grunt) {
'use strict';
var tests = 'tests/**/*.php',
watch = {},
shell = {};
grunt.file.expand(tests).forEach(function(filepath, index) {
watch[index] = {
files: [filepath],
tasks: [index]
};
shell[index] = {
command: 'vendor/bin/phpunit ' + filepath
};
grunt.registerTask(index, ['shell:' + index]);
});
grunt.initConfig({
watch: watch,
shell: shell
});
require('load-grunt-tasks')(grunt);
grunt.registerTask('default', [
'watch'
]);
};