Where I test exceptions because bad arguments happen.
Testing for the input
Sadly not all the input to a function or method can be type-hinted and implicitly checked for conformity.
Getting back to the WPSchedule_Schedule_Schedule
class, the star of the wp-schedule show, I want to test that the method schedule
will throw an Exception
when the hook name is not a string or is an empty string; right now the method is not complicated at all
public function schedule( WPSchedule_Time_TimeInterface $activation_time, WPSchedule_Interval_IntervalInterface $recurrence_interval, $hook, array $args = null ) {
wp_schedule_event( $activation_time->getTime(), $recurrence_interval->getInterval(), $hook, $args );
}
but, beside for time and interval type hinting, the $hook
argument could be anything.
I’ve set up a test method and a corresponding test provider for the purpose
public function badHookArgs() {
return [
[ 23 ],
[ '' ],
[ array() ],
[ new stdClass() ],
[ null ]
];
}
/**
* @test
* it should fail if calling with wrong or missing args
* @dataProvider badHookArgs
*/
public function it_should_fail_if_calling_with_wrong_or_missing_args( $badHookArg ) {
$now = time();
$time = FunctionMocker::replace( '\WPSchedule_Time_TimeInterface::getTime', $now );
$interval = FunctionMocker::replace( '\WPSchedule_Interval_IntervalInterface::getInterval', 500 );
$this->setExpectedException( '\Exception' );
$this->sut->schedule( $time, $interval, $badHookArg );
}
Where the test provider method will allow me to add tests at pleasure later should the need arise.
Without any check in place the tests will fail
Evolving the argument check
First line of defense is adding a check for the $hook
arg in the schedule
method
public function schedule( WPSchedule_Time_TimeInterface $activation_time, WPSchedule_Interval_IntervalInterface $recurrence_interval, $hook, array $args = null ) {
if ( ! is_string( $hook ) || empty( $hook ) ) {
throw new Exception( 'Hook name must be a non empty string' );
}
wp_schedule_event( $activation_time->getTime(), $recurrence_interval->getInterval(), $hook, $args );
}
Refactoring
The following phase of TDD is refactoring and that I will do to keep code DRY; I will need to check for the $hook
argument in any of the class methods accepting it as an argument and it makes sense to create a private
method to check for the argument that current and future methods of the class will be able to use; simply moving code I add this method to the class
private function checkHook( $hook ){
if ( ! is_string( $hook ) || empty( $hook ) ) {
throw new Exception( 'Hook name must be a non empty string' );
}
}
and will call it refactoring the schedule
method
public function schedule( WPSchedule_Time_TimeInterface $activation_time, WPSchedule_Interval_IntervalInterface $recurrence_interval, $hook, array $args = null ) {
$this->checkHook($hook);
wp_schedule_event( $activation_time->getTime(), $recurrence_interval->getInterval(), $hook, $args );
}
Good laziness
A good thing about TDD is that it makes me lazy: to write code I have to test it so the more code I write the more I have to write tests.
I don’t want to write more tests that’s needed.
Refactoring the argument checking into a method of its own allows me to test the whole class code, “cover it”, without having to test that any method accepting an $hook
argument throws if an invalid $hook
argument is passed to the method: I just need to test it in one place.
Do I really need to test such trivial code? Probably not right now but might need to do it in the future as the class evolves and the $hook
parameter becomes more complex and contextual: in that occurrence I will just need to modify/update the code in one place.
Even more laziness
The version of the project that’s on GitHub testifies to my laziness as a TDDer even more: I’ve developed an argument checking utility class that’s PHP 5.2 compatible and that I’m using to make the checks.
The checkHook
method evolves to
private function checkHook( $hook ) {
Arg::_( $hook, "Hook name" )->is_string()
->assert( ! empty( $hook ), 'Hook name must be a non empty string' );
}
Which will give the caller a more precise exception.
Why that? Because that class is tested already and I know it will work as intended. Same concept as before, just worse.
Next
Time to wrap up the wp-schedule
package.