I have gotten reasonably adept at using
HttpRequest
in Dart unit tests to make actual connections to test servers. But that is the HttpRequest
class from the dart:html
library, not in dart:io
. The
HttpRequest
class in dart:io
is very different from the class of the same name in dart:html
—the primary difference being that it is impossible to directly instantiate an HttpRequest
object in dart:io
. The server-side HttpRequest
, which lives in the dart:io
library, is only for encapsulating server requests. As such, only an HttpServer
can create a dart:io
HttpRequest
.Since the
dart:io
HttpRequest
encapsulates the request that a server sees, it cannot be used to establish a new connection to a server like its dart:html
counterpart (which replaces the venerable XMLHttpRequest
in Dart). So the question is, how do I write a unit test in dart:io
(i.e. doesn't use a web browser context)?I believe that the answer is with the
HttpClient
class.What I hope to do is start the server that I am trying to test in a setup block. Once that is ready, then I would like to test the
/stub
resource with HttpRequest
. To get the setup working, I need a
Future
to tell me when the server is ready. For that, I need my library's main()
method to return the result of the server's bind()
method, which is a future that completes when the server is ready:library plumbur_kruk; // imports and variable declaration... main() { return HttpServer.bind('127.0.0.1', 31337)..then((app) { // do server stuff here... }); }I use the method cascade operator (the “..”) to invoke
then()
on the future returned from the bind()
call. Instead of returning the result of the then()
call, the method cascade returns the object whose method is being invoked. In other words, I return the same Future
on which this then()
is being invoked.Back in my test, I create a test group that runs this
main()
method: group("Running Server", (){
var server;
setUp((){
return PlumburKruk.main()
..then((s){ server = s; });
});
// tests go here...
});
I use the same method cascade technique here that I used in the actual server. Once the main()
function's server is running, then I assign a local variable server
to the server passed from the bind()
method's future. More importantly, I return the same future from the setUp()
block. In Dart unit tests, returning a future from setUp()
blocks the tests from running until the future completes. In this case, my tests will block until the server is ready, which is exactly what I want.Once the server is up and running, I can write my test. I create a POST request from the
HttpClient
. To create the POST's body content, I have to use the future returned from the client to write()
the body: group("Running Server", (){
// setup...
test("POST /stub responds successfully", (){
new HttpClient().
postUrl(Uri.parse("http://localhost:31337/stub")).
then((request) {
request.write('{"foo": 42}');
return request.close();
});
});
});
I am still not testing anything here, but I have sent the request to the server and am ready to set the expectation that will test things. And here, I find something new—that a
then()
call on a future returns another future. It makes sense, but I had never really given it much thought. I have always performed some asynchronous action, then performed a single action when it was done. In this case, I need to wait for the
HttpClient()
to open a connection at the specified URL, then post some data, and once that is complete, then I do what I want with the response: group("Running Server", (){
var server;
setUp((){
return PlumburKruk.main()
..then((s){ server = s; });
});
tearDown(() => server.close());
test("POST /stub responds successfully", (){
new HttpClient().
postUrl(Uri.parse("http://localhost:31337/stub")).
then((request) {
request.write('{"foo": 42}');
return request.close();
}).
then(expectAsync1(
(response) {
expect(response.statusCode, 204);
}
));
});
});
I use the normal expectAsync1()
in the final then()
to block the test until the expected asynchronous call with one argument (the response in this case) fires.Just like that, I have another passing test against my test server:
➜ plumpbur-kruk git:(master) ✗ dart test/server_test.dart unittest-suite-wait-for-done PASS: Core Server non-existent resource results in a 404 PASS: Running Server POST /stub responds successfully All 2 tests passed. unittest-suite-successYay!
I like
HttpClient
, though I do wish that I could specify the POST body in the constructor or in the post()
method. I still dislike the two different HttpRequest
classes as it is easy to find myself looking at the wrong documentation. Nevertheless, I think that I am getting the hang of all this.Day #816
No comments:
Post a Comment