Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WP PHPUnit integration tests #269

Open
wants to merge 15 commits into
base: trunk
Choose a base branch
from

Conversation

alecgeatches
Copy link
Contributor

@alecgeatches alecgeatches commented Jan 2, 2025

Add WP_UnitTestCase test suite for WordPress integration testing.

Why?

Related to the work on improving data fallback in #258, I'd like to add tests to ensure that WordPress is using our data fallbacks when queries fail to load.

When blocks are rendered in a post, WordPress internally calls do_blocks(), which calls render_block(), and eventually calls into our code's BlockBindings::get_value(). An ideal test of our whole system could register a block and call do_block( $content ) or apply_filters( 'the_content', $content ) and verify the output matches our expected fallback.

However, we don't currently have a way to call do_blocks(), or do any similar WordPress code integration testing, because we don't load WordPress code directly.

We currently have three test suites:

  1. JavaScript tests in tests/src that test editor code and React components.

    JavaScript tests only apply to browser JavaScript, which is not relevant to PHP binding callbacks that we'd like to test here.

  2. PHPUnit tests in tests/inc. These are low-level PHP tests that mock WordPress functions where necessary, but do not actually interact with any WordPress code.

    These are not a good fit as they only interact with mocked PHP functions. If we want to see how content will actually be rendered by WordPress, can only test WordPress as well as we mock it. The current mocked behavior provides super-fast results and logical testing of PHP functions, but can not provide true WordPress integration results.

  3. End-to-end test (just one) in tests/e2e. This is a playwright-driven test that uses a headless browser to verify our Remote Block Data admin page loads correctly.

    E2E tests could be used to test this functionality, but these are very slow compared to other tests, can be much more flaky due to headless browser interaction, and are generally too high-level.

This PR adds a layer of tests between PHPUnit and E2E tests. WP PHPUnit uses an actual WordPress environment and interacts with WordPress code. These are a bit slower that pure logical PHPUnit tests, as there's more cleanup and setup than regular tests and code interacts with a real database. It may seem odd to have another layer of tests, but each provides a different purpose:

  1. PHPUnit: Fast, logical testing of data-driven functions.
  2. WP-PHPUnit: Integration testing with actual WordPress code. Similar to unit tests, but slower and provides a real WordPress to test against.
  3. E2E: Very slow tests with a browser. Useful for high-level happy path testing to ensure our plugin's UI works as expected.

The Lame Part

WP-PHPUnit does not currently support PHPUnit 10, but it seems like support will be coming soon. In order to make composer dependencies load, a handful of changes were needed:

  • Downgrade phpunit/phpunit from ^10 to ^9.

  • Some changes to phpunit.xml like removing displayDetailsOnTestsThatTriggerErrors as these are PHPUnit 10 only. They've been replaced with similar PHPUnit 9 configuration like convertErrorsToExceptions.

  • Added @preserveGlobalState disabled declarations to all @runInSeparateProcess tests in tests/inc/Editor/DataBinding/BlockBindingsTest.php. This fixed an error causing all tests with that property to fail due to non-serializable closure issues. For for information see PHPUnit 9's @runInSeparateProcess documentation:

    Note: By default, PHPUnit will attempt to preserve the global state from the parent process by serializing all globals in the parent process and unserializing them in the child process. This can cause problems if the parent process contains globals that are not serializable. See @preserveGlobalState for information on how to fix this.

Although this downgrade is lame, I have hope we'll be able to re-upgrade to PHPUnit 10 in the near future, and the changes required for the downgrade are fairly minimal and easy to revert.

Testing

This PR adds a single WP-PHPUnit test in tests/integration/Render/RenderTest.php. This test is similar to our Zip Code example block. It creates and registers a remote data block with a mocked API call, passes in raw HTML to WordPress, and ensures that values are rendered from the mocked data.

To test locally, ensure wp-env is running first and then use command:

npm run test:integration

I put this as an npm command because wp-env is called internally, but I'm happy to move it if that makes sense. Assuming this is merged, I'll plan to extend the current tests with data fallback tests in a separate PR.

@alecgeatches alecgeatches changed the title Add WP-PHPUnit integration tests Add WP PHPUnit integration tests Jan 2, 2025
return $dom;
}

private function assertIdInDomHasContent( DOMDocument $dom, string $html_id, string $expected_content ): void {
Copy link
Contributor Author

@alecgeatches alecgeatches Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will probably make sense to move these private functions to a helper class as WP-PHPUnit tests expand, but I've kept them in here until that's necessary (probably in the next testing PR).

$this->assertIdInDomHasContent( $dom, 'field-state', 'Test State' );
}

private function get_query_runner_with_response( array $response_data ) {
Copy link
Contributor Author

@alecgeatches alecgeatches Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of works like MockQueryRunner, but I wanted to make it easier to provide a JSON-like data result with multiple data values like it's used above:

$test_query_runner = self::get_query_runner_with_response([
    'post code' => 12345,
    'places' => [
        [
            'place name' => 'Test City',
            'state' => 'Test State',
        ],
    ],
]);

We could extend MockQueryRunner to provide this interface instead of single-value results, but I think it might make sense to have a separation between fully-mocked functions used by PHPUnit tests and those used by integration methods. Happy to discuss here, I'm not sure what makes most sense.

@alecgeatches alecgeatches self-assigned this Jan 2, 2025
@alecgeatches alecgeatches marked this pull request as ready for review January 2, 2025 23:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants