Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 121 lines (79 sloc) 5.293 kb
ad3baaf @vojtajina Add post "Testing Private State and Mocking Dependencies"
vojtajina authored
1 Title: Testing Private State and Mocking Dependencies
2 Author: Vojta Jina
3 Date: Sun Jan 08 2012 00:11:06 GMT-0800 (PST)
4 Node: v0.6.5
5
6
49d23dd @vojtajina Update link in old post "Testing private state…"
vojtajina authored
7 During Christmas I've been working on [Testacular] and found some tricks how to make my testing life
ad3baaf @vojtajina Add post "Testing Private State and Mocking Dependencies"
vojtajina authored
8 easier. It's nothing special at all, just a simple way **how to access private state of a module**
9 and **how to mock out some dependencies**. I've found these two techniques pretty usefull, so I
10 believe it might help someone else as well...
11
12
13
14 ### Why would you need to access private state of a module?
15
16 Private should be private, right? Yes, for sure. But during unit tests, it can be very helpful to
17 have access to private state of a module - I always try to cover functionality or bug at the lowest
18 possible level, because it's simply cheaper:
19
20 - faster test execution
21 - less code is required to bootstrap the test
22
23 Let's say we are building very simple static web server, the skeleton might look something like
24 this:
25
26 var http = require('http');
27
28 var handleRequest = function(request, response) {
29 // read file from fs and send response
30 };
31
32 exports.createServer = function() {
33 return http.createServer(handleRequest);
34 };
35
36 This module has only one public method `createServer`, so unless we make it public, we can't get
37 hold of anything else but this method. That sucks, because `HttpServer` doesn't have any public
38 method to call the handler, so we would have to send some data through socket to test it. That's way
39 too much effort, especially when you realize that the only code we really need to test is the
40 `handleRequest` function - everything else is just Node and we trust Node, because it's awesome. We
41 need to test **our** code - that's where all the bugs are.
42
43
44
45 ### Why would you need to mock out dependencies?
46
47 Some dependencies are cheap, some not. When our code uses modules like `util` or `path`, we are
48 fine. Nothing bad happens there. But when it comes to some other modules like `fs`, `net` or `http`,
49 it's totally different story. We simply don't want to deal with real filesystem in unit tests. There
50 are many reasons for that, such as:
51
52 - accessing file system is slow
53 - it requires seting up some state of filesystem
54 - there is only one instance of filesystem, so conflicts between different unit tests might happen
55
56 So we want our module to use something different - we call these objects test doubles (I actually
57 like using mock/stub/dummy definition from [G.Meszaros]). The question is, how can we persuade our
58 awesome module, to use **a different instance during testing and different instance in production**?
59
60 **Dependency Injection** is great for this - it wires all the pieces together (yep, it saves us lot
61 of work) - and more than that, it does allow us to use different instances during testing. Yep, DI is
62 just awesome! I actually think, that new languages such as [Dart] should support DI natively - in the
63 same way as they do support memory management.
64
65 Unfortunately, there is no DI in Node, at least I haven't found any sufficient implementation.
66 Writing a Dependency Injection framework is definitely a solution, but I was looking for something
67 faster...
68
69
70
71 ## Let's do it !
72
73
74 ### Module Loader
75
76 <testing-private-state-and-mocking-deps/module-loader.js>
77
78 **This is actually the code this post is all about :-D**
79
80 Instead of using Node's `require`, we use `loadModule` function, which reads the content of
81 requested module (javascript source file) and executes it on the `context` object. So all the
82 private state of the module is dumped into the `context` object and yay, we can access everything!
83 See [vm.runInNewContext] for more info.
84
85 Inside this `context` object, we defined our own `require` function, which means whenever the module
86 asks for a dependency, our `loadModule` will be called intead of Node's `require`. That's pretty
87 cool, because **we can decide, whether we want to return a mock or real module**, in which case we
88 delegate the request to Node's `require`.
89
90
91 ### Very simple web server example
92
93 <testing-private-state-and-mocking-deps/web-server.js>
94
95
96 ### Let's use it in test now
97
98 <testing-private-state-and-mocking-deps/web-server.test.js>
99
100 This is very simple example of unit testing `web-server` module, using `loadModule` function.
101
102 We can access both private functions as properties of `module` now, which is great, because we can
103 add more tests very easily. For example, you might have noticed, that `extensionFromUrl` won't
104 return correct extension when requested url contains query param. Piece of cake, just add a test
105 that covers this bug:
106
107 it('extensionFromUrl() should ignore query params', function() {
108 expect(module.extensionFromUrl('/some.html?param=ignored')).toBe('html');
109 });
110
111 The second test only asserts whether we set proper status code for existing file. We should assert
112 status code for non existing file as well as caching headers, content type header and many other
113 stuff. The important point here is, that **it's fast, because it doesn't touch the real filesystem
114 and still does test what needs to be tested - our code**.
115
116
49d23dd @vojtajina Update link in old post "Testing private state…"
vojtajina authored
117 [Testacular]: http://github.com/vojtajina/testacular/
ad3baaf @vojtajina Add post "Testing Private State and Mocking Dependencies"
vojtajina authored
118 [G.Meszaros]: http://xunitpatterns.com/Test%20Double.html
119 [vm.runInNewContext]: http://nodejs.org/docs/latest/api/vm.html#vm.runInNewContext
120 [Dart]: http://www.dartlang.org/
Something went wrong with that request. Please try again.