Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 226 lines (186 sloc) 8.693 kB
347c7b5 @dscape [nock] addded nock+tap article
dscape authored
1 Title: Easy HTTP Mock Testing with Nock and node-tap
2 Author: Nuno Job
3 Date: Tue Dec 06 2011 10:15:00 GMT
4 Node: v0.6.5
5
6 One of my first node.js libraries was [nano]: A no fuss CouchDB client based on the super pervasive [request]. In foresight that was a good idea, even though there's a ton of clients for CouchDB none of them is as simple as `nano`, and the fact that its based on `request` is great.
7
8 When you are writing a HTTP client you need to test with one (or several) HTTP endpoints. I was lazy about it so I choose to point nano to [iriscouch] and run the tests on real HTTP requests. This was a problematic but overall ok approach.
9
10 Then some weeks ago I started automating the tests using [travis]. And builds started to fail. To make this work and fix all the shortcomings of a direct connection to `iriscouch`. I needed a HTTP Mocking module.
11
12 By the way Travis is super cool. You should test all your node.js libraries with it. All you need to do is go to the site, sign in with github and place a `.travis.yml` file like this one in the root of your lib:
13
14 language: "node_js"
15 node_js:
16 - 0.4
17 - 0.6
18
19 ## Enter Nock
20
21 [Pedro Teixeira][nodetuts]'s [nock] allows you do HTTP Mock Testing while preserving the possibility to run the tests against a real http endpoint.
22
23 Let's start on this small [tap] test `sudo npm install tap nano nock`:
24
25 var nano = require('nano')('http://nodejsbug.iriscouch.com')
26 var test = require('tap').test;
27 var db = nano.use('testing_nock');
28
29 test('Insert a Document Into CouchDB', function(t) {
30 t.plan(4);
31 nano.db.create('testing_nock', function () {
32 db.insert({foo: "bar"},
33 function ensure_insert_worked_cb(err, doc) {
34 t.notOk(err, 'No errors');
35 t.ok(doc.ok, 'Contains ok');
36 t.ok(doc.rev, 'Rev exists');
37 t.ok(doc.id, 'Id exists');
38 });
39 });
40 });
41
42 If we save this in a file `test.js` we can run the tests and see they all work. We can even invoke the script with debugging turned on and inspect the HTTP requests/response flow `NANO_ENV=testing node test.js`:
43
44 { url: 'http://nodejsbug.iriscouch.com' }
45 >>
46 { method: 'PUT',
47 headers:
48 { 'content-type': 'application/json',
49 accept: 'application/json' },
50 uri: 'http://nodejsbug.iriscouch.com/testing_nock' }
51 <<
52 { err: null,
53 body: { ok: true },
54 headers:
55 { location: 'http://nodejsbug.iriscouch.com/testing_nock',
56 date: 'Thu, 01 Dec 2011 16:42:21 GMT',
57 'content-type': 'application/json',
58 'cache-control': 'must-revalidate',
59 'status-code': 201 } }
60 >>
61 { method: 'POST',
62 headers:
63 { 'content-type': 'application/json',
64 accept: 'application/json' },
65 uri: 'http://nodejsbug.iriscouch.com/testing_nock',
66 body: '{"foo":"bar"}' }
67 <<
68 { err: null,
69 body:
70 { ok: true,
71 id: 'f191a858a66828d8de66b3c974005346',
72 rev: '1-4c6114c65e295552ab1019e2b046b10e' },
73 headers:
74 { location: 'http://nodejsbug.iriscouch.com/testing_nock/f191a858a66828d8de66b3c974005346',
75 date: 'Thu, 01 Dec 2011 16:42:22 GMT',
76 'content-type': 'application/json',
77 'cache-control': 'must-revalidate',
78 'status-code': 201 } }
79 # Insert a Document Into CouchDB
80 ok 1 No errors
81 ok 2 Contains ok
82 ok 3 Rev exists
83 ok 4 Id exists
84
85 1..4
86 # tests 4
87 # pass 4
88
89 # ok
90
91 So `nano` gives you a way to actually see all the HTTP traffic that it creates and receives. This is great but I still need to write code to support these interactions.
92
93 With `nock` this is super simple:
94
95 var nano = require('nano')('http://nodejsbug.iriscouch.com')
96 var nock = require('nock'); // we require nock
97 var test = require('tap').test;
98 var db = nano.use('testing_nock');
99
100 // tell nock to record the http interactions
101 nock.recorder.rec();
102
103 test('Insert a Document Into CouchDB', function(t) {
104 t.plan(4);
105 nano.db.create('testing_nock', function () {
106 db.insert({foo: "bar"},
107 function ensure_insert_worked_cb(err, doc) {
108 t.notOk(err, 'No errors');
109 t.ok(doc.ok, 'Contains ok');
110 t.ok(doc.rev, 'Rev exists');
111 t.ok(doc.id, 'Id exists');
112 });
113 });
114 });
115
116 Running the tests returns:
117
118 $ node test.js
119
120 <<<<<<-- cut here -->>>>>>
121
122 nock('nodejsbug.iriscouch.com')
123 .put('/testing_nock')
124 .reply(412, "{\"error\":\"file_exists\",\"reason\":\"The database could not be created, the file already exists.\"}\n", { server: 'CouchDB/1.1.1 (Erlang OTP/R14B04)',
125 date: 'Thu, 01 Dec 2011 17:43:30 GMT',
126 'content-type': 'application/json',
127 'content-length': '95',
128 'cache-control': 'must-revalidate' });
129
130 <<<<<<-- cut here -->>>>>>
131
132 <<<<<<-- cut here -->>>>>>
133
134 nock('nodejsbug.iriscouch.com')
135 .post('/testing_nock', "{\"foo\":\"bar\"}")
136 .reply(201, "{\"ok\":true,\"id\":\"8b787a6a1c2476ef9a2eed069e000ff0\",\"rev\":\"1-4c6114c65e295552ab1019e2b046b10e\"}\n", { server: 'CouchDB/1.1.1 (Erlang OTP/R14B04)',
137 location: 'http://nodejsbug.iriscouch.com/testing_nock/8b787a6a1c2476ef9a2eed069e000ff0',
138 date: 'Thu, 01 Dec 2011 17:43:31 GMT',
139 'content-type': 'application/json',
140 'content-length': '95',
141 'cache-control': 'must-revalidate' });
142
143 <<<<<<-- cut here -->>>>>>
144
145 # Insert a Document Into CouchDB
146 ok 1 No errors
147 ok 2 Contains ok
148 ok 3 Rev exists
149 ok 4 Id exists
150
151 1..4
152 # tests 4
153 # pass 4
154
155 # ok
156
157 So now all we need to do is add these nock http mocks and we are done:
158
159 var nano = require('nano')('http://nodejsbug.iriscouch.com')
160 var nock = require('nock'); // we require nock
161 var test = require('tap').test;
162 var db = nano.use('testing_nock');
163
164 var couch = nock('nodejsbug.iriscouch.com')
165 .put('/testing_nock')
166 .reply( 412
167 , "{ \"error\":\"file_exists\""+
168 ", \"reason\":\"The database could not be created, the file" +
169 " already exists.\"}\n"
170 , { server: 'CouchDB/1.1.1 (Erlang OTP/R14B04)'
171 , date: 'Thu, 01 Dec 2011 17:43:30 GMT'
172 , 'content-type': 'application/json'
173 , 'content-length': '95'
174 , 'cache-control': 'must-revalidate' })
175 .post('/testing_nock', "{\"foo\":\"bar\"}")
176 .reply(201
177 , "{ \"ok\":true" +
178 ", \"id\":\"8b787a6a1c2476ef9a2eed069e000ff0\"" +
179 ", \"rev\":\"1-4c6114c65e295552ab1019e2b046b10e\"}\n"
180 , { server: 'CouchDB/1.1.1 (Erlang OTP/R14B04)'
181 , location: 'http://nodejsbug.iriscouch.com/testing_nock/'
182 + '8b787a6a1c2476ef9a2eed069e000ff0'
183 , date: 'Thu, 01 Dec 2011 17:43:31 GMT'
184 , 'content-type': 'application/json'
185 , 'content-length': '95'
186 , 'cache-control': 'must-revalidate' });
187
188 test('Insert a Document Into CouchDB', function(t) {
189 t.plan(4);
190 nano.db.create('testing_nock', function () {
191 db.insert({foo: "bar"},
192 function ensure_insert_worked_cb(err, doc) {
193 t.notOk(err, 'No errors');
194 t.ok(doc.ok, 'Contains ok');
195 t.ok(doc.rev, 'Rev exists');
196 t.ok(doc.id, 'Id exists');
197 });
198 });
199 });
200
201 All working, happy nocking! :)
202
203 $ node test.js
204 # Insert a Document Into CouchDB
205 ok 1 No errors
206 ok 2 Contains ok
207 ok 3 Rev exists
208 ok 4 Id exists
209
210 1..4
211 # tests 4
212 # pass 4
213
214 # ok
215
216 For the inquisitive types this is how nock intercepts the http requests: Nock uses the the fact that `node` caches modules that you `require` and overrides the behavior of the default HTTP request in node. So any request you do will be filtered by Nock. Check out the [source code][intercept] if you want to see more details.
217
218 [nano]: https://github.com/dscape/nano
219 [request]: https://github.com/mikeal/request
220 [iriscouch]: http://iriscouch.com
221 [bug]: https://github.com/joyent/node/issues/1569
222 [travis]: http://travis-ci.org/#!/dscape/nano
223 [nock]: https://github.com/pgte/nock
224 [tap]: https://github.com/isaacs/node-tap
225 [nodetuts]: http://nodetuts.com
226 [intercept]: https://github.com/pgte/nock/blob/master/lib/intercept.js
Something went wrong with that request. Please try again.