“All the Word that's fit to Press”

Unit Testing Custom WordPress REST API Endpoints

I’ve written a lot about the WordPress REST API for Torque, but one thing I have not covered is unit testing custom APIs. That is exactly what this article is.

I used a PHP program run the code and compare the results to an expected value. If you want to learn more, I would recommend reading Pippin Williamson’s series on WordPress unit testing. It’s quite excellent and covers how to setup your local environment for testing and how to write tests.

For example here is a very simple test:

PHP

1

2

3

4

5

6

7

8

9

<?php

functionanswer(){

return42;

}

classTest_42extendsWP_UnitTestCase{

publicfunctiontestAnswer(){

$this->assertEquals(answer(),42);

}

}

It’s a contrived example, but we can see that it’s using the assertEquals() method to ensure that this function is, in fact, returning 42. Now we have a test to prove why it did what we expected. The point is that assertEquals() compares the result of the function to the expected value I provided and “asserts” they are equal.

Here’s a different example where I have created a CRUD class that can only store numbers less than 42. Then there are two tests to prove that the number 10 can be stored and retrieved and one to prove that the number 100 can not be:

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

<?php

classlimited_crud{

publicstaticfunctionsave($value){

if(absint($value)<42){

update_option('hishawn',absint($value));

}else{

update_option('hishawn',0);

}

}

publicstaticfunctionget(){

returnget_option('hishawn');

}

}

classTest_42extendsWP_UnitTestCase{

publicfunctiontestValidInput(){

$value=10;

limited_crud::save($value);

$this->assertSame(limited_crud::get(),$value);

}

publicfunctiontestInvalidInput(){

$value=1000;

limited_crud::save($value);

$this->assertSame(limited_crud::get(),0);

}

}

Notice that I switched from assertEquals() to assertSame(). When using assertEquals() the type and value are checked. In this case, I’m fine with type changing to string from integer. The assertSame() check ignores type. The test assertSame( 10, ’10’ ); would pass, while assertEquals( 10, ’10’ ); would fail.

What To Test

One thing I always advocate when designing custom REST APIs using WordPress, that the callbacks for each endpoint should only handle passing input data to another class or function for the actual functionality of the route, and format the response. Doing this abides by the separation of concerns.

This kind of isolation means that each system can be tested separately. If the CRUD system is tested, then the API routes that wrap it can be safely tested assuming that the CRUD system works. This is just like how we can assume that the underlying REST API infrastructure from core works.

So the unit tests for the REST API route will just need to do two things:

Verify that the routes are added properly

Verify that the responses are as expected.

Again, we are assuming that everything else in the application is tested, so that’s all we need. Let’s walk through both parts.

Testing That Endpoints Are Added

Before we can test that an endpoint works properly we need to make sure it has been added. This type of test is fairly simple, but it can catch errors that happen when register_rest_route() is used improperly.

It’s important, that when setting up unit tests for the REST API, we create a mock instance of WP_REST_Server. If we don’t we can’t really test the system. In unit tests, the method, setUp() is called before the tests are run. This allows us to set things up first. Here is how I setup my tests to ensure the REST API is properly loaded:

PHP

1

2

3

4

5

6

7

8

9

10

11

<?php

classTest_APIextendsWP_UnitTestCase{

publicfunctionsetUp(){

parent::setUp();

/** @var WP_REST_Server $wp_rest_server */

global$wp_rest_server;

$this->server=$wp_rest_server=new\WP_REST_Server;

do_action('rest_api_init');

}

}

This populates the global $wp_rest_server and then does the rest_api_init action. Without that, none of our tests would fail, and the failure would be because of bad test design.

Now that that is in place, I like to setup a class property with the name of my route, with its namespace so I can test if it is in the routes added by the REST API. Here is updated code that gives me that:

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

classTest_APIextendsWP_UnitTestCase{

/**

* Test REST Server

*

* @var WP_REST_Server

*/

protected$server;

protected$namespaced_route='caldera/forms';

publicfunctionsetUp(){

parent::setUp();

/** @var WP_REST_Server $wp_rest_server */

global$wp_rest_server;

$this->server=$wp_rest_server=new\WP_REST_Server;

do_action('rest_api_init');

}

}

Once that’s in place I can test two things using WP_REST_Server::get_routes(). That method returns an array of routes. The array keys are the routes, for example, “wp/v2/posts” and those arrays have the arguments and callbacks for the routes. So, I can test that there is a key of that array for my route and that the contents of that array key are correct:

This test, which can easily be adapted to work with multiple routes by extending it and changing the value of the namespaced_route property, ensures that routes are added properly. That’s good, especially as it prevents future changes from accidentally removing those routes.

Testing Route Responses

The other thing I said we needed to do was test that the API could generate the right responses. When I first thought about creating these types of tests, I thought I would have to make an HTTP request to the route and then test the response. However, that’s too complicated.

Instead, I assume that the WordPress REST API infrastructure works, and create a mock request and check its response. This is how the tests for the default endpoints works. Reading their source is how I figured it out. For example, look at this test, copied from a test of the default post endpoint:

This code covers testing a bunch of different scenarios. Each time a request is created with a new instance of the WP_Rest_Request() class and dispatching it using WP_Rest_Server to create a response that can be verified. This is a great example of how you can get posts in the format of a REST API response without making a loop back request.

Let’s put this to use to test a route. Let’s assume we have a route that takes an ID at the end of the URL and return that item’s name. We would want a test like this:

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

classTest_APIextendsWP_UnitTestCase{

/**

* Test REST Server

*

* @var WP_REST_Server

*/

protected$server;

protected$namespaced_route='caldera/forms';

publicfunctionsetUp(){

parent::setUp();

/** @var WP_REST_Server $wp_rest_server */

global$wp_rest_server;

$this->server=$wp_rest_server=new\WP_REST_Server;

do_action('rest_api_init');

}

publicfunctiontest_name_route(){

$request=newWP_REST_Request('GET','/api/v2/name');

$response=$this->server->dispatch($request);

$this->assertEquals(200,$response->get_status());

$data=$response->get_data();

$this->assertArrayHasKey('name',$data);

$this->assertEquals('shawn',$data['name']);

}

}

What I’m testing here is that my request returned a 200 status code, that it created an array with the key name, and that array key’s value was correct. Whatever internal logic made this happen, I’m not testing. I should test it elsewhere, but not in this test.

There are 4 comments

Join the conversation

Love it? Hate it? Tell us what you think by leaving a comment!And, don't worry, your email address will not be published.

CommentPlease enter a comment

Name *Please enter your name

Email *Please enter your email

Website

Save my name, email, and website in this browser for the next time I comment.

Continue the conversation via email!Get only replies to your comment, the best of the rest, as well as a daily recap of all comments on this post. No more than a few emails daily, which you can reply to/unsubscribe from directly from your inbox.

Torque is a news site featuring all things WordPress. We are dedicated to informing new and advanced WordPress professionals about the industry. Check in daily for tutorials, interviews, news, and more.