WRITING TESTABLE PLUGINS? WHY? These days the systems we are building are more and more complex. We press one button and get a result, but a million things happen behind the scenes. If something goes wrong, you don't want to get one of those "Sorry, an error occurred!" messages. You want to know exactly what happened. Good unit tests will give you that.
WRITING TESTABLE PLUGINS? WHY? Unit tests allow us to design the system we are building before we build it. As we build it we will probably want to change things. Good unit tests allow us to safely change our code without breaking anything that came before.
To reduce dependencies: 1. Pass input to your functions rather than having your function fetch input themselves. 2. Put dependencies in variables that can be overridden.
Allowing dependencies to be overwritten is called "Dependency Injection". Tests can isolate one part of code from another by replacing the dependencies.
Code getting input from a function dependency: function get_the_content( $id ) { $post = get_post( $id ); return $post->post_content; } A test of that code with a mock function dependency: \Spies\mock_function( 'get_post' )->that_returns( (object) [ 'ID' => 123, 'post_content' => 'hello' ] ); $result = get_the_content( 123 ); $this->assertEquals( 'hello', $result );
Code getting input from an object dependency: class MyPlugin { public $getter; public function __construct( $getter = null ) { $this->getter = $getter ? $getter : new Getter(); } public function get_data_content() { $post = $this->getter->get_post(); return $post->post_content; } } A test of that code with a mock object dependency: $getter = \Spies\mock_object(); $getter->add_method( 'get_post' )->that_returns( (object) [ 'ID' => 123, 'post_content' => 'hello' ] ); $plugin = new MyPlugin( $getter ); $result = $plugin->get_data_content(); $this->assertEquals( 'hello', $result );
Generators are a shortcut for object creation. Static Generators are ok. class MyPlugin { public $getter; public function __construct( $getter ) { $this->getter = $getter; } public static function get_instance() { return new MyPlugin( new Getter() ); } } They don't always need to be tested because we can test the object creation manually.
Static functions that have no dependencies and no side-effects are ok. class MyPlugin { public static function is_published_post( $post ) { return $post->post_status === 'publish'; } } These can be tested without mocks.
Here's an example of using a filter. function read_data( $post ) { add_filter( 'my_data', function() use ( &$post ) { return $post->post_content; } ); } function return_data() { return apply_filters( 'my_data', $post->post_content ); } You can write tests to be sure the filter is added and applied. mock_function( 'apply_filters' )-> when_called->with( 'my_data' )->will_return( 'foobar' ); $this->assertEquals( 'foobar', return_data() );
Example: a shortcode handler add_shortcode( 'otherpages', ... ); ) my_shortcode doesn't really say what it does. process_my_shortcode is better, but still ambiguous about what the function returns. get_markup_from_shortcode tells us what the input and output will be.
Here we use a spy to make sure a shortcode is added correctly. function add_my_shortcode() { add_shortcode( 'otherpages', 'get_markup_from_shortcode' ); } $spy = \Spies\get_spy_for( 'add_shortcode' ); add_my_shortcode(); $this->assertTrue( $spy->was_called_with( 'otherpages', \Spies\any() ); );
"Unit tests isolate failures. Even if a product contains millions of lines of code, if a unit test fails, you only need to search that small unit under test to find the bug." https://googletesting.blogspot.co.uk/2015/04/just-say-no-to-more-end-to-end-tests.html