Test-Driven Gutenberg Blocks
In the last part, we created some test for components that do not actually do anything. We are following a "red/ green" pattern. The tests are failing, our CI should be showing a red dot for failure. Now we commit working components until we see green in our CI.
Save Callback
The test for the save callback called for passing the attributes
and className
props. The <DisplayValue/>
component accespts props of value
and className
. So, this component is really just translating props:
//src/components/Saveimport React from "react";import { DisplayValue } from "./app/DisplayValue";export const Save = ({ attributes, className }) => {return <DisplayValue value={attributes.value} className={className} />;};
Re-run your tests. This should cause the snapshot tests for this component to fail. Take a look at the new JSON, if it looks right, accept the change.
Edit Callback
In the tests for the edit callback, we used four props: attributes
, setAttributes
, className
, and clientId
. The last two we can use for the class and id of the <EditValue/>
component.
The other two props, attributes
and setAttributes
, we can not use as-is. We need to pass just attributes.value
and we need a change handler function for just that attribute.
We can compose the change handler from setAttributes()
. Keep in mind that setAttributes()
works like React.Component.setState()
, not React.useState()
.
const onChange = value => setAttribute({ value });
If you want to, you can write this outside of the function scope and unit test it. I think that's overkill. Here is the full component:
import React, { Fragment } from "react";import { EditValue } from "./app/EditValue";import { DisplayValue } from "./app/DisplayValue";import { InspectorControls } from "@wordpress/block-editor";export const Editor = ({ attributes, setAttributes, className, clientId }) => {//Change handlerconst onChange = value => setAttributes({ value });//current valueconst { value } = attributes;return (<Fragment><InspectorControls><EditValueclassName={`${className}-editor`}id={clientId}value={value}onChange={onChange}/></InspectorControls><DisplayValue value={value} className={className} /></Fragment>);};
If these tests pass, we know that the componets should work together.
e2e Testing Gutenberg Blocks
Now that we know that our components work correctly, we need to make sure our plugin can be activated and used to add our block to the post editor. We do this by creating a WordPress site, automating the process of clicking the buttons to do that and asserting that the end result is as expected.
End to end testing for WordPress blocks should get a full section of this site later. It's a huge topic, I'll rush through it here to get you started.
When we right these test, we assume that all of the components work. These tests are the most like how a user would use the plugin.
Useing Puppetter To Automate Chrome
This test tool uses headless Chrome, and is controlled using puppetter. The WordPress e2e test tools are a wrapper over Puppeteer. So, if you're not sure about how it works, check the Puppetter docs.
Easiest if you have WordPress running locally in Docker like core does
Copy my copy of core's local development
Helpful Links
How To Setup Up WordPress End To End Tests
To make things easier, add the WordPress e2e test utilities:
# Add e2e test utilitiesyarn add @wordpress/e2e-test-utils
You will see these utility functions shortly. They wrap repeditive steps, such as activating the plugin or adding a block to a post.
Configure Jest
A seperate Jest config is needed to make sure it does NOT run unit tests.
const defaultConfig = require("./node_modules/@wordpress/scripts/config/jest-unit.config.js");module.exports = {//use the default from WordPress for everything......defaultConfig,//Except test ignore, where we need to ignore our e2e test directorytestPathIgnorePatterns: ["/.git/", "/node_modules/", "<rootDir>/e2e"]};
This is based on WordPress core's e2e tests
Create a directory called e2e
. That is where e2e tests will go.
Test That The Block Works
In the directory e2e
try adding one test file. We are still using Jest as the test runner, so everything you learned about writing tests still applies, we just have new tools.
Speaking of which, start by importing helper functions to make naviagating WordPress easier:
import {insertBlock,getEditedPostContent,createNewPost,activatePlugin,deactivatePlugin} from "@wordpress/e2e-test-utils";
Now we can test that we can add the block.
This test starts by activating the plugin and then afterwords we deacvitave it. This acomplishes two things. First, it resets everything between tests, which is good. Second, it makes sure that the plugin can be activated without errors.
describe("Block", () => {//file path for pluginconst plugin = "plugin-name/plugin-name.php";beforeEach(async () => {await activatePlugin(plugin);});afterEach(async () => {await deactivatePlugin(plugin);});it("Can add block", async () => {await createNewPost();await insertBlock("Josh Block");expect(await getEditedPostContent()).toMatchSnapshot();});});
Do NOT e2e Test Everything
e2e tests ensure that the system works toghether. They are a compliment to unit tests and integration tests that are faster to run and easier to setup.
I think of e2e tests as coverage for what the other tests can not cover.