Summary of the proposed change
Implementation tips for the mock server approach for E2E API testing.
Link to supporting documentation, GitHub tickets, etc.
- Dart HttpServer
- Dart MockWebServer API reference
What problem is this project solving?
Implement E2E 3rd-party API testing.
Identify success metrics and measurable goals.
- Simple to integrate
- Simple to write tests
Identify what's not in scope.
Integration tips are not in scope because they depend on implementations of clients under tests.
Explain and diagram the technical design
Slack API adapter
→ Slack API client
→ HTTP request
→ Mock web server
→ Request handler
→ HTTP response
Identify risks and edge cases
HTTP requests must be made directly to the mock server. The client HTTP requests must be redirected to the mock server, otherwise the requests will be sent to the real server.
What will the proposed API look like?
Create a mock server that listens for HTTP requests and responds with mock data.
class SlackMockServer extends ApiMockServer {
@override
List<RequestHandler> get handlers => [
RequestHandler.post(
path: '/services/00001/00001/00001',
dispatcher: _handlePost,
),
];
void _handlePost(HttpRequest request) {
// implementation
request.response
..statusCode = HttpStatus.ok
..write(mockResponse)
..close();
}
}
Redirect requests to the mock server within the group of tests (implementation may vary depending on the client).
void main() {
group('SlackClient', () {
final slackClient = SlackClient();
final slackMockServer = SlackMockServer();
String serverUrl;
setUpAll(() async {
await slackMockServer.init();
serverUrl = slackMockServer.url;
});
tearDownAll(() async {
await slackMockServer.close();
});
test('test1', () {
final message = SlackMessage(text: 'test');
final response = slackClient.sendMessage('$serverUrl/services/00001/00001/00001', message);
final expected = SlackResult.success();
expect(response, completion(equals(expected)));
});
});
}
What is the project blocked on?
No blockers.
What will be impacted by the project?
All E2E API testing strategy implementations are impacted.
Summarize alternative designs (pros & cons)
- MockWebServer package:
- Pros
- Server interactions out-of-the-box. MockWebServer encapsulates server binding.
- Provides a lot of ways to mock the response. This is possible to set a handler method for requests, to enqueue response and, finally, to set a default response.
- Cons
- It is very easy to accidentally introduce hidden dependencies between tests that should be isolated (see example below).
void main() { group('SlackClient', () { final slackClient = SlackClient(); final mockWebServer = MockWebServer(); String serverUrl; setUpAll(() async { await mockWebServer.start(); serverUrl = mockWebServer.url; }); tearDownAll(() async { await mockWebServer.shutdown(); }); test('test1', () { mockWebServer.dispatcher = (request) async { return MockResponse()..httpCode = HttpStatus.ok; }; final message = SlackMessage(text: 'test'); final response = slackClient.sendMessage('$serverUrl/services/00001/00001/00001', message); final expected = SlackResult.success(); expect(response, completion(equals(expected))); }); test('test2', () { mockWebServer.enqueueResponse(MockResponse()..httpCode = HttpStatus.badRequest); final message = SlackMessage(); final response = slackClient.sendMessage('$serverUrl/services/00001/00001/00001', message); final expected = SlackResult.error(); // fails since `MockWebServer.dispatcher` has greater priority in implementation than enqueued responses expect(response, completion(equals(expected))); }); }); }
- MockWebServer is badly typed. Almost all methods of MockWebServer return
dynamic
as denoted by Dart Analyzer though the actual runtime type is different (see example below). Therefore, using MockWebServer without knowing how its methods are implemented is error prone.void main() { // start() type is `dynamic Function()` final start = mockWebServer.start(); // runtime type of the result is `Future<dynamic>` print(start.runtimeType); // actual type from the implementation is `Future<void> Function()` // enqueue() type is `dynamic Function({...})` final enqueueResponse = mockWebServer.enqueue(httpCode: 200); // runtime type of the result is `Null` print(enqueueResponse.runtimeType); // actual type from the implementation is `void Function({...})` }
- It is very easy to accidentally introduce hidden dependencies between tests that should be isolated (see example below).
- Pros
Document milestones and deadlines.
DONE:
- Added implementing E2E API testing document.
NEXT:
- Integrate E2E API testing into the project.
What was the outcome of the project?
Work in progress.