Skip to content

Testing

Arun Prakash edited this page Nov 3, 2026 · 3 revisions

Testing

package:wordpress_client/testing.dart provides a Dio adapter that serves canned responses, so code that uses the client can be tested without a live site or real network.

import 'package:dio/dio.dart';
import 'package:wordpress_client/testing.dart';
import 'package:wordpress_client/wordpress_client.dart';

Build a client on the mock

final adapter = MockWordpressAdapter();

final client = WordpressClient.fromDioInstance(
  baseUrl: Uri.parse('https://example.com/wp-json/wp/v2'),
  instance: Dio()..httpClientAdapter = adapter,
  bootstrapper: (b) => b.build(),                // add auth, retries, cache, ... as in production
);

Script responses

Routes match on HTTP method and path (a String is matched as a path suffix; pass a RegExp for patterns). The newest route wins.

adapter
  ..reply('GET', '/posts', MockResponse.paged(
    [{'id': 1, 'title': {'rendered': 'Hello'}}],
    total: 1,
    totalPages: 1,
  ))
  ..reply('POST', '/posts', MockResponse.wpError(
    statusCode: 403,
    code: 'rest_cannot_create',
    message: 'Sorry, you are not allowed to create posts.',
  ))
  ..reply('GET', '/pages', const MockResponse(statusCode: 200, body: []));
MockResponse Result
MockResponse(statusCode:, body:, headers:, delay:) Any response. body can be a map, list or string
MockResponse.paged(items, total:, totalPages:, delay:) List with X-WP-Total and X-WP-TotalPages; delay simulates latency (handy for testing de-duplication and concurrency limits)
MockResponse.wpError(statusCode:, code:, message:) A WordPress error body
MockResponse.connectionError() A transport failure (NetworkFailure)
MockResponse.timeout() A timeout (NetworkFailure(kind: timeout))

Dynamic and sequential answers:

adapter.on('GET', RegExp(r'/posts/\d+$'), (req) {
  final id = int.parse(req.path.split('/').last);
  return MockResponse(body: {'id': id, 'title': {'rendered': 'Post $id'}});
});

adapter.replySequence('GET', '/pages', [
  MockResponse.timeout(),                       // first call fails
  const MockResponse(body: []),                 // then succeeds (the last entry repeats)
]);

MockWordpressAdapter(strict: true) throws a StateError for a request without a route; the default answers 404 rest_no_route.

Assert on what was sent

await client.posts.create(CreatePostRequest(title: 'Hi'));

expect(adapter.callCount(method: 'POST', path: '/posts'), 1);
expect(adapter.lastRequest!.body.toString(), contains('Hi'));
expect(adapter.lastRequest!.headers['Authorization'], isNotNull);   // when a default authorization is set
expect(adapter.requests.map((r) => r.uri.path), contains('/wp-json/wp/v2/posts'));
adapter.clearRequests();

RecordedRequest has method, uri, path, headers, body (its type depends on the request: a map, a JSON string, form data).

Test retries without waiting

final client = WordpressClient.fromDioInstance(
  baseUrl: baseUrl,
  instance: Dio()..httpClientAdapter = adapter,
  bootstrapper: (b) => b
      .withRetryPolicy(RetryPolicy(maxAttempts: 3, sleep: (_) async {}))   // no real delay
      .build(),
);

adapter.replySequence('GET', '/posts', [
  MockResponse.wpError(statusCode: 503, code: 'unavailable'),
  MockResponse.paged([], total: 0, totalPages: 0),
]);

final r = await client.posts.list(ListPostRequest());
expect(r.isSuccessful, isTrue);
expect(r.extra?[WordpressRequest.metaAttempts], 2);

Test your own code

Inject the client (or a factory) into your repositories and pass this one in tests. Because the mock goes through the real pipeline, middlewares, caching, auth headers and typed failures behave as in production.

Integration tests against a real WordPress

The repository's own integration tests run against WordPress in Docker. To run them locally (Docker with the compose plugin):

docker compose -f docker-compose.wordpress.yml up -d --wait
docker compose -f docker-compose.wordpress.yml run --rm setup     # installs WordPress and the plugins

export WP_APP_PASSWORD=$(docker compose -f docker-compose.wordpress.yml run --rm -T setup \
  wp user application-password create admin local --porcelain)
export WP_BASE_URL=http://localhost:8080/wp-json/wp/v2 WP_USERNAME=admin

dart test -t integration
docker compose -f docker-compose.wordpress.yml down -v

The site is set to the local environment type, so Application Passwords work over plain HTTP. WP_PLUGINS on the setup service picks the plugins to install (default: the JWT plugin and WPGraphQL). CI runs the same steps on every push.

Do this for your own app's tests too when the mock is not enough: point the client at the container and use the same variables.

Clone this wiki locally