Skip to content

Commit 00d6022

Browse files
committed
Added async support
1 parent 4d1b8f9 commit 00d6022

File tree

5 files changed

+152
-42
lines changed

5 files changed

+152
-42
lines changed

documentation/assertions/function/to-be-valid-for-all.md

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
An assertion that runs the subject function up to 100 times with different
1+
An assertion that runs the subject function up to 300 times with different
22
generated input. If the subject function fails an error is thrown.
33

44
Let's tests that
@@ -144,3 +144,25 @@ expect(function (actions) {
144144
expect(dequeued, 'to equal', enqueued);
145145
}, 'to be valid for all', actions);
146146
```
147+
148+
Support for asynchronous testing by returning a promise from the subject
149+
function:
150+
151+
```js#async:true
152+
expect.use(require('unexpected-stream'));
153+
154+
return expect(function (text) {
155+
return expect(
156+
text,
157+
'when piped through',
158+
[
159+
require('zlib').Gzip(),
160+
require('zlib').Gunzip()
161+
],
162+
'to yield output satisfying',
163+
'when decoded as', 'utf-8',
164+
'to equal',
165+
text
166+
);
167+
}, 'to be valid for all', g.string);
168+
```

documentation/index.md

+24
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,30 @@ define(['unexpected', 'unexpected-check'], funtion (unexpected, unexpectedCheck)
150150
Notice that the [chance](www.chancejs.com) library some we configured with the
151151
name `chance`.
152152

153+
## Asynchronous testing
154+
155+
Support for asynchronous testing by returning a promise from the subject
156+
function:
157+
158+
```js#async:true
159+
expect.use(require('unexpected-stream'));
160+
161+
return expect(function (text) {
162+
return expect(
163+
text,
164+
'when piped through',
165+
[
166+
require('zlib').Gzip(),
167+
require('zlib').Gunzip()
168+
],
169+
'to yield output satisfying',
170+
'when decoded as', 'utf-8',
171+
'to equal',
172+
text
173+
);
174+
}, 'to be valid for all', g.string);
175+
```
176+
153177
## Source
154178

155179
The source for this plugin can be found on

lib/unexpected-check.js

+58-39
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@
3434
return {
3535
name: 'unexpected-check',
3636
installInto: function (expect) {
37+
var promiseLoop = function (condition, action) {
38+
return expect.promise(function (resolve, reject) {
39+
var loop = function () {
40+
if (!condition()) return resolve();
41+
return action()
42+
.then(loop)
43+
.catch(reject);
44+
};
45+
46+
loop();
47+
});
48+
};
49+
3750
expect.addAssertion('<function> to be valid for all <object>', function (expect, subject, options) {
3851
var generators = options.generators;
3952
var maxIterations = options.maxIterations || 300;
@@ -48,15 +61,6 @@
4861
args: args
4962
};
5063

51-
try {
52-
subject.apply(null, args);
53-
} catch (e) {
54-
generators = generators.map(function (g, i) {
55-
return g.shrink ? g.shrink(args[i]) : g;
56-
});
57-
task.error = e;
58-
}
59-
6064
return task;
6165
}
6266

@@ -69,51 +73,66 @@
6973
function createTasks() {
7074
var tasks = [];
7175
var errors = 0;
72-
for (var i = 0; i < maxIterations && errors < maxErrors; i += 1) {
73-
if (errors > 0 && !hasShrinkableGenerators()) {
74-
break;
75-
}
76+
var i = 0;
7677

78+
return promiseLoop(function () {
79+
return (
80+
i < maxIterations &&
81+
errors < maxErrors &&
82+
(errors === 0 || hasShrinkableGenerators())
83+
);
84+
}, function () {
7785
var task = createTask();
7886
tasks.push(task);
79-
if (task.error) {
87+
88+
return expect.promise(function () {
89+
return subject.apply(null, task.args);
90+
}).then(function () {
91+
i++;
92+
}, function (err) {
93+
generators = generators.map(function (g, i) {
94+
return g.shrink ? g.shrink(task.args[i]) : g;
95+
});
96+
task.error = err;
8097
errors++;
81-
}
82-
}
83-
return tasks;
98+
i++;
99+
});
100+
}).then(function () {
101+
return tasks;
102+
});
84103
}
85104

86-
var tasks = createTasks();
105+
return createTasks().then(function (tasks) {
106+
var failedTasks = tasks.filter(function (task) {
107+
return task.error;
108+
});
87109

88-
var failedTasks = tasks.filter(function (task) {
89-
return task.error;
90-
});
110+
if (failedTasks.length > 0) {
111+
var bestFailure = failedTasks[failedTasks.length - 1];
112+
113+
expect.errorMode = 'bubble';
114+
expect.fail(function (output) {
115+
output.error('Ran ').jsNumber(tasks.length).sp()
116+
.error(tasks.length > 1 ? 'iterations' : 'iteration')
117+
.error(' and found ').jsNumber(failedTasks.length).error(' errors').nl()
118+
.error('counterexample:').nl(2);
91119

92-
if (failedTasks.length > 0) {
93-
var bestFailure = failedTasks[failedTasks.length - 1];
94-
95-
expect.errorMode = 'bubble';
96-
expect.fail(function (output) {
97-
output.error('Ran ').jsNumber(tasks.length).sp()
98-
.error(tasks.length > 1 ? 'iterations' : 'iteration')
99-
.error(' and found ').jsNumber(failedTasks.length).error(' errors').nl()
100-
.error('counterexample:').nl(2);
101-
102-
output.indentLines();
103-
output.i().block(function (output) {
104-
output.text('Generated input: ').appendItems(bestFailure.args, ', ');
105-
output.nl(2).block(function (output) {
106-
output.appendErrorMessage(bestFailure.error);
120+
output.indentLines();
121+
output.i().block(function (output) {
122+
output.text('Generated input: ').appendItems(bestFailure.args, ', ');
123+
output.nl(2).block(function (output) {
124+
output.appendErrorMessage(bestFailure.error);
125+
});
107126
});
108127
});
109-
});
110-
}
128+
}
129+
});
111130
});
112131

113132
expect.addAssertion('<function> to be valid for all <function+>', function (expect, subject) {
114133
expect.errorMode = 'bubble';
115134

116-
expect(subject, 'to be valid for all', {
135+
return expect(subject, 'to be valid for all', {
117136
generators: Array.prototype.slice.call(arguments, 2)
118137
});
119138
});

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424
"lodash.escape": "3.1.0",
2525
"lodash.unescape": "3.1.0",
2626
"mocha": "2.3.4",
27-
"unexpected": "10.5.1",
27+
"unexpected": "10.13.3",
2828
"unexpected-documentation-site-generator": "^4.0.0",
29-
"unexpected-markdown": "^1.4.0"
29+
"unexpected-markdown": "^1.4.0",
30+
"unexpected-stream": "2.0.3"
3031
}
3132
}

test/unexpected-check.spec.js

+44
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,48 @@ describe('unexpected-check', function () {
157157
' // ^\n' +
158158
' ]');
159159
});
160+
161+
it('supports asynchronous bodies', function () {
162+
return expect(
163+
expect(function (items, i) {
164+
return expect.promise(function () {
165+
expect(items, 'not to contain', i);
166+
}).delay(1);
167+
}, 'to be valid for all', arrays, g.integer({ min: -20, max: 20 }))
168+
, 'to be rejected with',
169+
'Ran 143 iterations and found 20 errors\n' +
170+
'counterexample:\n' +
171+
'\n' +
172+
' Generated input: [ -4 ], -4\n' +
173+
'\n' +
174+
' expected [ -4 ] not to contain -4\n' +
175+
'\n' +
176+
' [\n' +
177+
' -4 // should be removed\n' +
178+
' ]');
179+
});
180+
181+
it('supports a mix between synchronous and asynchronous bodies', function () {
182+
return expect(
183+
expect(function (items, i) {
184+
if (i % 2 === 0) {
185+
expect(items, 'not to contain', i);
186+
} else {
187+
return expect.promise(function () {
188+
expect(items, 'not to contain', i);
189+
}).delay(1);
190+
}
191+
}, 'to be valid for all', arrays, g.integer({ min: -20, max: 20 }))
192+
, 'to be rejected with',
193+
'Ran 143 iterations and found 20 errors\n' +
194+
'counterexample:\n' +
195+
'\n' +
196+
' Generated input: [ -4 ], -4\n' +
197+
'\n' +
198+
' expected [ -4 ] not to contain -4\n' +
199+
'\n' +
200+
' [\n' +
201+
' -4 // should be removed\n' +
202+
' ]');
203+
});
160204
});

0 commit comments

Comments
 (0)