I have not done much server-side testing in Dart yet. Tonight seems like as good a time as any to kick the tires.
Unlike client-side testing in Dart, there are a number of solid testing options available to me. The most obvious is to actually run the server and verify that the responses returned are what I expect. The other option is to convert the
server.dart
file into a Dart library so that the individual functions and classes can be probed directly. I will try the latter tonight since it just seems more fun.To import a library for test, the thing being imported needs to be a… library. To convert
test_server.dart
into a library, I need only add the library statement to the top of the file:library plumbur_kruk; // imports and variable declarations... main() { HttpServer.bind('127.0.0.1', 31337).then((app) { app.listen((HttpRequest req) { log(req); // handle requests here... notFoundResponse(req); }); }); }The library name does not matter as long as it is alphanumeric and underscores.
With that, I write a test that imports my new library:
library plumbur_kruk_test; import 'package:unittest/unittest.dart'; import 'dart:io'; import 'test_server.dart' as PlumburKruk; main(){ group("Core Server", (){ test("non-existent resource results in a 404", (){ var req = new HttpRequest(); }); }); }In the
main()
entry point, I have written my first test that verifies that I can get a 404 response back from the non-existent request handling code. Actually, there is no real test there—I am just wondering if I can create a new HttpRequest
object. In fact, I cannot:
➜ plumpbur-kruk git:(master) ✗ dart test/server_test.dart unittest-suite-wait-for-done ERROR: Core Server non-existent resource results in a 404 Test failed: Caught Cannot instantiate abstract class HttpRequest: url 'file:///home/chris/repos/plumpbur-kruk/test/server_test.dart' line 15 core_server_tests.<anonymous closure>.<anonymous closure> file:///home/chris/repos/plumpbur-kruk/test/server_test.dart 15:21 ...Bah! I am on the fence, but I do not think that I like this. It bothers me a little that I cannot directly create this object. It bothers me more that I can create an
HttpRequest
instance on the client side. In this case, I am getting the
HttpRequest
class from the dart:io
library. When I code Dart in the browser, a different HttpRequest
class comes from the dart:html
library. HttpRequest
from dart:io
is meant to handle HTTP requests in a server environment. HttpRequest
from dart:html
is a replacement for the venerable XMLHttpRequest
object for performing so-called AJAX operations. I cannot create instances of
HttpRequest
from dart:io
—only a server's listen
stream can do that. It is still possible to make HTTP requests from the command-line in Dart—the HttpClient
class does that. Naturally, it does not have an HttpRequest
object for me to use, preferring to expose them in futures.Basically, I am stuck. I cannot create an
HttpRequest
object for my server test. But I can create something the quacks like an HttpRequest
duck. Rather than building a fake class manually, I am going to give the mock
library a go. I may be trying one too many new things at this point, but what the hey?The mock library is already included in the
unittest
library, so there is no need to update my pubspec.yaml
. It already depends on unittest
:name: plumbur_kruk #... dependencies: unittest: anySince I already have
unittest
there is also no need to install with Dart Pub. I should be good to create a mock instance of
HttpRequest
. I update my test to import the mock library, then I create a MockHttpRequest
as implementing HttpRequest
, and finally create an instance of this mock class:library plumbur_kruk_test; import 'package:unittest/unittest.dart'; import 'package:unittest/mock.dart'; import 'dart:io'; import 'package:plumbur_kruk/server.dart' as PlumburKruk; class MockHttpRequest extends Mock implements HttpRequest {} main(){ group("Core Server", (){ test("non-existent resource results in a 404", (){ var req = new MockHttpRequest(); }); }); }And that works. Or at least it compiles and successfully creates an instance of
MockHttpRequest
:➜ plumpbur-kruk git:(master) ✗ dart test/server_test.dart unittest-suite-wait-for-done PASS: Core Server non-existent resource results in a 404 All 1 tests passed. unittest-suite-successOf course, I am not really testing anything. I am going to need a second mock class to test the response—a
MockHttpResponse
class:class MockHttpRequest extends Mock implements HttpRequest {} class MockHttpResponse extends Mock implements HttpResponse {}To make use of this, my test gets a little more complicated. I start by creating a mock HttpResponse instance and a mock HttpRequest instance:
var response = new MockHttpResponse();
var req = new MockHttpRequest()
..when(callsTo('get response')).alwaysReturn(response);
In addition to creating the mock request object, I give it some behavior. I tell it that, when it sees calls to the response
getter, it should always return my mock HTTP response object. This syntax feels a little awkward, but it is fairly readable.Next, I call the
noResponseFound()
method in my actual test server with the mock request object: PlumburKruk.notFoundResponse(req);
Last, I need to check the mock request object's “logs” to see how many times the statusCode
setter was told to assign a 404 value: response.
getLogs(callsTo('set statusCode', 404)).
verify(happenedOnce);
Here, I am verifying that, according the logs that the mock hold, the setter was called once with a value of 404.In the actual server method, it really is only set once:
notFoundResponse(req) { HttpResponse res = req.response; res.statusCode = HttpStatus.NOT_FOUND; res.close(); }So, if I have done this correctly, my test should pass:
➜ plumpbur-kruk git:(master) ✗ dart test/server_test.dart unittest-suite-wait-for-done PASS: Core Server non-existent resource results in a 404 All 1 tests passed. unittest-suite-successYay! That seems legit. To be sure, I temporarily break the
notFoundResponse()
function to set a 200 status code:notFoundResponse(req) { HttpResponse res = req.response; res.statusCode = HttpStatus.OK; res.close(); }And, that does indeed cause a test failure:
➜ plumpbur-kruk git:(master) ✗ dart test/server_test.dart unittest-suite-wait-for-done FAIL: Core Server non-existent resource results in a 404 Expected set statusCode(<404>) to be called 1 times but: was called 0 times. LogEntryList.verify package:unittest/mock.dart 568:11 main.<anonymous closure>.<anonymous closure> file:///home/chris/repos/plumpbur-kruk/test/server_test.dart 23:15 ...I am pretty excited to finally have put the mock library in Dart to some good use—all the more so because the resultant test is pretty solid. It would still make some sense to contrast this approach with a real HTTP client and server, which I will likely tackle tomorrow.
Day #815
No comments:
Post a Comment