I am building a simple WordPress plugin and I am looking to add unit tests using PHPUnit, I have the following class code:
<?php
namespace App;
class MyPlugin {
public function __construct()
{
add_action('admin_enqueue_scripts', [$this, 'enqueueAdminScripts']);
add_action('admin_menu', [$this, 'createAdminMenu']);
add_action('admin_init', [$this, 'settings']);
}
public function enqueueAdminScripts()
{
// Code here
}
public function createAdminMenu()
{
// Code here
}
public function settings()
{
// Code here
}
}
And this is my test file:
<?php
use AppMyPlugin;
use PHPUnitFrameworkTestCase;
class MyPlugin extends TestCase {
protected $myPlugin;
protected function setUp(): void
{
$this->myPlugin = new MyPlugin();
}
public function testRegister()
{
$this->assertInstanceOf('AppMyPlugin', $this->myPlugin);
}
}
However I am always getting the error Error: Call to undefined function Appadd_action()
What is the best way of mocking the add_action
calls purely within PHPUnit without having to rely on a third party solution?
Any help with this would be greatly appreciated.
2
Answers
Below is a very simple mock of the hook system. It doesn’t take into account priorities, and it is more modernized for expected usage so it might not support some previous edge cases.
You’ll notice that
add_action
actually calls the exact same code asadd_filter
, and that’s what core actually does.The gist is that there’s a global array. You could store this in your base class if you wanted, too. WordPress backs their stuff with objects, too, but I’m simplifying things here.
You can see it in action by using normal WordPress code:
Demo here: https://3v4l.org/DAVl0
All that said, if you don’t manually call
do_action
orapply_filters
in your code, don’t both with the bulk of this. You can use the functions, just leave their bodies empty. More often than not, it is WordPress or a plugin that is invoking those methods, and if you aren’t invoking WordPress or calling those functions, there’s no need for logic or even the global array. What you probably want is:I’d say this depends a lot what you’d actually want to test (and therefore call).
Reading your question you would like to put WordPress itself out of the equation to essentially test your code.
That is testing a plugin not as an integration test.
Now WordPress does not make it exactly easy to test but you could try to get as far as possible. I’d say the issue you describe in the question lays in the constructor:
This constructor only works within WordPress. But now you want to test it without. Well, easy:
Problem sovled. Now you’d say, this is overly clever, right? The plugin needs to be initialized. True:
With this, you still have one central way to setup your plugin while you’re able in your unit-tests to test calling the concrete functions. The
new
operator (in your test) and the way you’ve written the constructor does not stand in your way any longer and the code became testable.If those as well depend on WordPress functions, you’ll run into the same problem thought. However the functionality you actually may want to test here might not entirely, so extracting the code that does not into methods of its own, allow actual unit-tests of those independent methods (those that have no side-effects on the global scope which WordPress uses).
And I’d say your nose is good here: At the end of the day you want as many of these pure methods (without side-effects) and less those that depend on global state and WordPress as a dependency.
You could do other tests (e.g. integrating with WordPress) with the WordPress base test case class to not reinvent the wheel. To only do that for certain methods, group your tests in different directories, e.g.
test/unit
andtest/wordpress
.Then you can decide what kind of test you want to write.