Imported from tekartik/common_node.dart (
http_node_test/skills/tekartik-http-node-test-setup/SKILL.md). Install upstream withnpx skills add tekartik/common_node.dart --skill tekartik-http-node-test-setup. Copyright stays with the author.
tekartik_http_node_test: HTTP client conformance on Node.js (tekartik_http_node_test)
tekartik_http_node_test has no lib/: it is the runnable test package of
the common_node.dart repo (http_node_test) that runs the shared
tekartik_http_test client suites, plus the dart-lang
http_client_conformance_tests, against the Node.js clients of
tekartik_http_node. Use it as the template for validating any
HttpClientFactory under node.
Guidelines
- Nothing to depend on or import: clone
https://github.com/tekartik/common_node.dart, rundart pub getat the repo root (a pub workspace) and work insidehttp_node_test/.dart_test.yamldeclares thenodeandvmplatforms, sodart testruns both anddart test -p nodeonly the node ones.nodemust be on the PATH; node tests are compiled with dart2js. - A similar package of your own needs, as dev dependencies,
tekartik_http_node(githttps://github.com/tekartik/common_node.dart,path: http_node),tekartik_httpandtekartik_http_test(githttps://github.com/tekartik/http.dart,path: http/http_test),tekartik_platform_node,test, and for compiled node scriptsbuild_runner,build_web_compilersandtekartik_build_node(githttps://github.com/tekartik/build_node.dart,path: packages/build_node). - Client factories under test, all from
tekartik_http_node:httpClientFactoryNode(http_client_node.dart),httpClientFactoryNodeFetch(http_client_node_fetch.dart) andhttpClientFactoryUniversal(http_client_universal.dart, node when compiled to JS,dart:ioon the VM). All three areHttpClientFactoryvalues withnewClient(); accessing them off their platform throwsUnimplementedError, so avm || nodetest must catch it (see the API guard example). - Shared suite:
runEchoServerClientTests(httpClientFactory)frompackage:tekartik_http_test/echo_server_client_test.dartstarts its own echo server (spawned in the VM even from a node test) and exercises the client against it. This is the one call a new client implementation must pass.run(httpFactory)frompackage:tekartik_http_test/http_test.dartis the full client+server suite — on node onlyhttpFactoryMemorycan run it, becausetekartik_http_nodehas no server. - Conformance:
package:http_client_conformance_tests(githttps://github.com/dart-lang/http,ref: master,path: pkgs/http_client_conformance_tests) is called function by function rather than through its owntestAll, because the node client does not pass everything. Node client limits, expressed as the flags this package uses:canStreamRequestBody: false,canWorkInIsolates: false,supportsFoldedHeaders: false,supportsMultipartRequest: false,canSendCookieHeaders: false,canReceiveSetCookieHeaders: false,redirectAlwaysAllowed: true,preservesMethodCase: true; andtestRequestMethods/testResponseHeadersare skipped entirely. - Cross-runtime check:
echoServe(httpServerFactoryIo, 0)frompackage:tekartik_http_test/test_server.dartbinds a realdart:ioecho server on a free port and returns anHttpServerState(uri,close()). Passstate.urito a child process through the environment variable named byuriVarKey(package:tekartik_http_test/test_server_client_test.dart), then run the same runner file withdart test -p vmanddart test -p nodeviapackage:process_run'sShell. The runner reads the variable withplatform.environmentfrompackage:tekartik_platform_node/context_universal.dart(which works on both), builds anEchoServerClient(factory: ..., uri: ...)and callsrun(client)fromtest_server_client_test.dart. Name the runner*_runner.dart, not*_test.dart, sodart testdoes not pick it up on its own. Guard the node half withisNodeSupportedSync(package:dev_build/build_support.dart). build.yamlsendsnode/**through thebuild_web_compilers|entrypointbuilder withcompiler: dart2js;tool/run_ci.dartcallsnodePackageRunCi('.')frompackage:tekartik_build_node/package.dart, and the othertool/run_node_*.dartscripts are one-liners runningdart test -p node [-N "<name>"] <file>throughShell.- Anti-patterns: expecting a server from
tekartik_http_node(client only, usehttpServerFactoryIoon the VM orhttpFactoryMemory); running the node suites withoutnodeon the PATH; putting the cross-runtime runner in a_test.dartfile; asserting on cookies, multipart or request streaming for the node client.
Examples
The node client suites (fetch and default)
@TestOn('node')
library;
import 'package:tekartik_http_node/http_client_node.dart';
import 'package:tekartik_http_node/http_client_node_fetch.dart';
import 'package:tekartik_http_test/echo_server_client_test.dart';
import 'package:test/test.dart';
void main() {
group('node', () {
runEchoServerClientTests(httpClientFactoryNode);
});
group('node_fetch', () {
runEchoServerClientTests(httpClientFactoryNodeFetch);
});
}
Memory factory on node, and the API guard on vm or node
@TestOn('vm || node')
library;
import 'package:tekartik_common_utils/env_utils.dart';
import 'package:tekartik_http/http_memory.dart';
import 'package:tekartik_http_node/http_client_node.dart';
import 'package:tekartik_http_test/http_test.dart' as http_test;
import 'package:test/test.dart';
Future<void> main() async {
// The full client + server suite: only the memory factory can serve.
group('memory', () {
http_test.run(httpFactoryMemory);
});
test('httpClientFactoryNode', () {
try {
httpClientFactoryNode;
// Reached only when compiled to javascript.
expect(isRunningAsJavascript, isTrue);
} on UnimplementedError catch (_) {}
});
}
The conformance subset that passes on node
@TestOn('node')
library;
import 'package:http_client_conformance_tests/http_client_conformance_tests.dart';
import 'package:tekartik_http_node/http_client_node.dart';
import 'package:test/test.dart';
Client newClient() => httpClientFactoryNode.newClient();
void main() {
group('client conformance tests', () {
testRequestBody(newClient);
testRequestBodyStreamed(newClient, canStreamRequestBody: false);
testResponseBody(newClient);
testResponseBodyStreamed(newClient);
testRequestHeaders(newClient);
testResponseStatusLine(newClient);
testRedirect(newClient, redirectAlwaysAllowed: true);
testServerErrors(newClient);
testCompressedResponseBody(newClient);
testMultipleClients(newClient);
testMultipartRequests(newClient, supportsMultipartRequest: false);
testClose(newClient);
testIsolate(newClient, canWorkInIsolates: false);
// testRequestMethods / testResponseHeaders currently fail on node.
});
}
Cross-runtime runner: one io echo server, vm and node clients
// test/test_client_only_universal_runner.dart -- NOT a _test.dart file.
import 'package:tekartik_http_node/http_client_universal.dart';
import 'package:tekartik_http_test/test_server.dart';
import 'package:tekartik_http_test/test_server_client_test.dart';
import 'package:tekartik_platform_node/context_universal.dart';
import 'package:test/test.dart';
Future<void> main() async {
var uri = platform.environment[uriVarKey];
group('echo client', () {
if (uri != null) {
run(
EchoServerClient(
factory: httpClientFactoryUniversal,
uri: Uri.parse(uri),
),
);
}
}, skip: uri == null ? 'skipped for no uri' : false);
}
Driving that runner from a vm test
@TestOn('vm')
library;
import 'package:dev_build/build_support.dart';
import 'package:process_run/shell.dart';
import 'package:tekartik_http_io/http_server_io.dart';
import 'package:tekartik_http_test/test_server.dart';
import 'package:tekartik_http_test/test_server_client_test.dart';
import 'package:test/test.dart';
Future<void> main() async {
late HttpServerState echoServeState;
setUpAll(() async {
echoServeState = await echoServe(httpServerFactoryIo, 0);
});
tearDownAll(() async {
await echoServeState.close();
});
Future<void> runOn(String platformName) async {
var shell = Shell(
environment: ShellEnvironment()
..vars[uriVarKey] = echoServeState.uri.toString(),
);
await shell.run(
'dart test -p $platformName test/test_client_only_universal_runner.dart',
);
}
test('vm', () => runOn('vm'));
test('node', () async {
if (isNodeSupportedSync) {
await runOn('node');
}
});
}
Common mistakes
- Calling
run(httpFactoryNode): there is no nodeHttpFactory, the node package is client only — userunEchoServerClientTests. - Touching
httpClientFactoryNodein avm || nodetest without catchingUnimplementedError. - Using
http_client_conformance_tests' owntestAll: several of its suites fail on node, call the individualtest*functions instead. - Naming the cross-runtime runner
*_test.dart, which makesdart testrun it without the server uri in the environment. - Running the node tests without
nodeon the PATH, or forgetting that they go through dart2js so a compile error shows up only atdart testtime.