-
Notifications
You must be signed in to change notification settings - Fork 1
Testing
For testing our WP-FastEndpoints router we are going to use pest/php.
Pest is a testing framework that makes it super easy to test functionality in PHP, that's why we are going to use it here. However, if you have a preference for some other testing framework, the some principles should apply 😊
Full source code can be found at matapatos/wp-fastendpoints-my-plugin »
First, let's add all the necessary testing dependencies:
composer require mockery/mockery --dev # For mocking classes/functions
composer require dingo-d/wp-pest --dev # Adds Pest support for integration tests
For testing our plugin, we are going to assume the following structure:
my-plugin
│ my-plugin.php
│ composer.json
│
└───src
│ (...)
│
└───tests
│ bootstrap.php # Loads WordPress for integration tests
│ Helpers.php # (optional) Helper functions
│ Pest.php # Pest configuration file
│
└───Integration
│ PostsApiTest.php
│
└───Unit
PostsApiTest.php
Integration tests, are a bit tricky to set up.
The following needs to happen in order to successfully run them:
- Load WordPress
- Replace the default TestCase class with one with enhanced WordPress functionalities (e.g. to easily create users or posts)
- Create the REST server and boot it using the
rest_api_init
hook
However, thanks to wp-pest most of this trouble is no longer an issue. By simply running the command bellow, it will automatically pull the WordPress version you want and also set up the tests directory for you.
./vendor/bin/wp-pest setup plugin --plugin-slug my-plugin --wp-version 6.4.4
PS: If you use matapatos/wp-fastendpoints-my-plugin
you can use the already configured composer setup:wp:6.x
commands
If you take a closer look at the resultant tests structure you might notice that is slightly different from the one previously mentioned. These changes are not mandatory and so, feel free to skip this section ⏩
The main reason of these differences is to allow us to run tests without the need to always specify a group of tests. Those changes include:
/**
* tests/Helpers.php
*/
namespace MyPlugin\Tests;
class Helpers
{
/**
* Checks if weather we want to run integration tests or not
*/
public static function isIntegrationTest(): bool
{
return isset($GLOBALS['argv']) && in_array('--group=integration', $GLOBALS['argv'], true);
}
}
/**
* tests/Integration/*Test.php
*/
namespace MyPlugin\Tests\Integration;
use MyPlugin\Tests\Helpers;
// Needs to add this check to every Integration test file
if (! Helpers::isIntegrationTest()) {
return;
}
Now that everything is configured we can start creating integration tests:
test('Create a new post', function () {
// Create user with correct permissions
$userId = $this::factory()->user->create();
$user = get_user_by('id', $userId);
$user->add_cap('publish_posts');
// Make request as that user
wp_set_current_user($userId);
$request = new \WP_REST_Request('POST', '/my-plugin/v1/posts');
$request->set_body_params([
'post_title' => 'My testing message',
'post_status' => 'publish',
'post_type' => 'post',
'post_content' => '<p>Message body</p>',
]);
$response = $this->server->dispatch($request);
expect($response->get_status())->toBe(200);
$postId = $response->get_data();
// Check that the post details are correct
expect(get_post($postId))
->toBeInstanceOf(\WP_Post::class)
->toHaveProperty('post_title', 'My testing message')
->toHaveProperty('post_status', 'publish')
->toHaveProperty('post_type', 'post')
->toHaveProperty('post_content', '<p>Message body</p>');
})->group('api', 'posts');
Here, we take advantage of the existent testing factories to create a single user with the necessary capability to publish posts. Then, we make mimic a REST request from that given user, and lastly, we check if that blog post was created.
As an example of a unit test, we are going add a test to check the 1) request payload schema used and 2) the necessary user permissions on the endpoint that allows a user to create a new blog post.
We could have separated each assertion in its own unit test but for the sake of simplicity we are going to make both of them in the same test.
test('Creating post endpoint has correct permissions and schema', function () {
// Create endpoint mock
$endpoint = Mockery::mock(Endpoint::class);
// Assert that the request payload schema passed is correct
$endpoint
->shouldReceive('schema')
->once()
->with('Posts/CreateOrUpdate')
->andReturnSelf();
// Assert that user permissions are correct
$endpoint
->shouldReceive('hasCap')
->once()
->with('publish_posts');
// Create router. Make sure that var name matches your router variable
$router = Mockery::mock(Router::class)
->makePartial();
// Assert that router endpoint is called
$router
->shouldReceive('post')
->once()
->with('/', Mockery::type('callable'))
->andReturn($endpoint);
// Needed to attach endpoints
require \ROUTERS_DIR.'/Posts.php';
})->group('api', 'posts');
The reason we are able to make the assertions above is
due to this line.
Specially, regarding this part $router ??
. This allows us to replace our original router with our mocked version.
As you might notice, this is just pure PHP code nothing magical! 🪄
FastEndpoints was created by André Gil and is open-sourced software licensed under the MIT license.