UNPKG

11.4 kBMarkdownView Raw
1[![TODO board](https://imdone.io/api/1.0/projects/5b1adecc1883d42a1fbf805d/badge)](https://imdone.io/app#/board/bahmutov/snap-shot-it)
2
3# snap-shot-it
4
5> Smarter snapshot utility for Mocha and BDD test runners + data-driven testing!
6
7[![NPM][npm-icon] ][npm-url]
8
9[![Build status][ci-image] ][ci-url]
10[![semantic-release][semantic-image] ][semantic-url]
11[![js-standard-style][standard-image]][standard-url]
12
13## Why
14
15This tool makes [snapshot testing][snapshot testing] for Mocha (and other BDD)
16frameworks quick and painless. This module spies on global `it` function,
17which allows it to accurately get test information (beating static code parsing
18done in [snap-shot][snap-shot]); it should work in transpiled code.
19
20[snapshot testing]: https://glebbahmutov.com/blog/snapshot-testing/
21
22This package uses [snap-shot-compare](https://github.com/bahmutov/snap-shot-compare)
23to display object and text difference intelligently.
24
25This function also includes [data-driven][data-driven] testing mode,
26similar to [sazerac][sazerac], see [Data-driven testing](#data-driven-testing)
27section below.
28
29[data-driven]: https://hackernoon.com/sazerac-data-driven-testing-for-javascript-e3408ac29d8c#.9s4ikt67d
30[sazerac]: https://github.com/mikec/sazerac
31
32## Install
33
34Requires [Node](https://nodejs.org/en/) version 4 or above.
35
36```sh
37npm install --save-dev snap-shot-it
38```
39
40## Use
41
42Example from [spec.js](src/spec.js)
43
44```js
45const snapshot = require('snap-shot-it')
46describe('example', () => {
47 it('works', () => {
48 snapshot(add(10, 20))
49 snapshot('a text message')
50 return Promise.resolve(42).then(snapshot)
51 })
52})
53```
54
55Run Mocha tests, then open the created
56[__snapshots__/spec.js](__snapshots__/spec.js) file
57
58```js
59exports['example works 1'] = 30
60
61exports['example works 2'] = "a text message"
62
63exports['example works 3'] = 42
64```
65
66Suppose you change the resolved value from `42` to `80`
67
68```js
69const snapshot = require('snap-shot-it')
70describe('example', () => {
71 it('works', () => {
72 snapshot(add(10, 20))
73 snapshot('a text message')
74 return Promise.resolve(80).then(snapshot)
75 })
76})
77```
78
79The test will fail
80
81```
821) example works:
83 Error: 42 !== 80
84```
85
86The error message should intelligently handle numbers, objects, arrays,
87multi-line text, etc.
88
89## Returned value
90
91The returned value includes saved value (after any transformations) and saved snapshot name. Usually it is spec name + index, or could be exact name
92
93```js
94const out = snapshot('my name', 42)
95// {value: 42, key: 'my name'}
96```
97
98## Advanced use
99
100You can see the saves snapshot values by running with environment variable
101
102```bash
103SNAPSHOT_SHOW=1 npm test
104```
105
106You can see snapshot values without writing them into the snapshot file
107
108```bash
109SNAPSHOT_DRY=1 npm test
110```
111
112You can update snapshot values
113
114```bash
115SNAPSHOT_UPDATE=1 npm test
116```
117
118You can use the following aliases: `SNAPSHOT_UPDATE=1`, `SNAPSHOTS_UPDATE=1` or `SNAP_SHOT_UPDATE=1`.
119
120## Sorted snapshots
121
122If you want to sort saved snapshots alphabetically inside each snapshot file, run with
123
124```bash
125SNAPSHOT_SORT=1 npm test
126```
127
128You can also set the config option in the package.json file
129
130```json
131{
132 "config": {
133 "snap-shot-it": {
134 "sortSnapshots": true
135 }
136 }
137}
138```
139
140Hopefully sorting snapshots would help when updating them.
141
142## Named snapshots
143
144Renaming tests might lead to confusion and pruning snapshots. You can name the snapshots
145yourself
146
147```js
148const value = 42
149snapshot('my name', value)
150```
151
152The snapshots will be saved as
153
154```js
155exports['my name'] = 42
156```
157
158**Note** you should make sure that the name is unique per spec file.
159
160### Shared snapshot name
161
162If you **do want** to share a named snapshot value from several places or tests in the same spec file, you need to pass an option when calling `snapshot`. The the first snapshot is saved, and the next ones will just compare against the value.
163
164```js
165snapshot('my shared snapshot', value, { allowSharedSnapshot : true })
166// some time later
167snapshot('my shared snapshot', value, { allowSharedSnapshot : true })
168```
169
170## Pruning
171
172If the test run is successful and executed _all_ tests (there was no `.only`) then snapshots without a test are pruned. You can skip pruning by running with environment variable
173
174```bash
175SNAPSHOT_SKIP_PRUNING=1 npm test
176```
177
178## Data-driven testing
179
180Writing multiple input / output pairs for a function under test quickly
181becomes tedious. Luckily, you can test a function by providing multiple
182inputs and a single snapshot of function's *behavior* will be saved.
183
184```js
185// checks if n is prime
186const isPrime = n => ...
187it('tests prime', () => {
188 snapshot(isPrime, 1, 2, 3, 4, 5, 6, 7, 8, 9)
189})
190```
191
192The saved snapshot file will have clear mapping between given input and
193produced result
194
195```js
196// snapshot file
197exports['tests prime 1'] = {
198 "name": "isPrime",
199 "behavior": [
200 {
201 "given": 1,
202 "expect": false
203 },
204 {
205 "given": 2,
206 "expect": true
207 },
208 {
209 "given": 3,
210 "expect": true
211 },
212 {
213 "given": 4,
214 "expect": false
215 },
216 {
217 "given": 5,
218 "expect": true
219 },
220 ...
221 ]
222}
223```
224
225You can also test functions that expect multiple arguments by providing
226arrays of inputs.
227
228```js
229const add = (a, b) => a + b
230it('checks behavior of binary function add', () => {
231 snapshot(add, [1, 2], [2, 2], [-5, 5], [10, 11])
232})
233```
234
235Again, the snapshot file gives clear picture of the `add` behavior
236
237```js
238// snapshot file
239exports['checks behavior of binary function add 1'] = {
240 "name": "add",
241 "behavior": [
242 {
243 "given": [
244 1,
245 2
246 ],
247 "expect": 3
248 },
249 {
250 "given": [
251 2,
252 2
253 ],
254 "expect": 4
255 },
256 {
257 "given": [
258 -5,
259 5
260 ],
261 "expect": 0
262 },
263 {
264 "given": [
265 10,
266 11
267 ],
268 "expect": 21
269 }
270 ]
271}
272```
273
274See [src/data-driven-spec.js](src/data-driven-spec.js) for more examples.
275
276## Debugging
277
278Run with environment variable `DEBUG=snap-shot-it ...` to see log messages.
279Because under the hood it uses [snap-shot-core][snap-shot-core] you might
280want to show messages from both libraries with `DEBUG=snap-shot* ...`
281
282## Data callbacks
283
284You can pass your own NPM modules as `pre-compare`, `compare` and `store` functions using `package.json`. For example, to use both local and 3rd party NPM modules
285
286```json
287{
288 "config": {
289 "snap-shot-it": {
290 "pre-compare": "./pre-compare",
291 "compare": "snap-shot-compare",
292 "store": "./store"
293 }
294 }
295}
296```
297
298Each NPM module in this case should export a definition of a function that matches the expected core function
299
300- `pre-compare` is simply an identity or transformation function
301- `compare` should match [snap-shot-core#compare-function](https://github.com/bahmutov/snap-shot-core#compare-function), for example see [snap-shot-compare](https://github.com/bahmutov/snap-shot-compare)
302- `store` is another identity or transformation function, see [snap-shot-core#store-function](https://github.com/bahmutov/snap-shot-core#store-function)
303
304## Nested snapshots
305
306By default, all snapshots are stored in the same folder `__snapshots__`, which can lead to name clashes. You can set an option in your package.json file to create a nested folder structure inside `__snapshots__` folder that mimics the spec structure. Use `config > snap-shot-it` object in the package.json file.
307
308```json
309{
310 "config": {
311 "snap-shot-it": {
312 "useRelativePath": true
313 }
314 }
315}
316```
317
318input spec files
319
320```
321specs/
322 spec.js
323 subfolder/
324 spec2.js
325```
326
327result should be
328
329```
330__snapshots__/
331 specs/
332 spec.js
333 subfolder/
334 spec2.js
335```
336
337## Examples
338
339### TypeScript
340
341An example using [ts-mocha](https://github.com/piotrwitek/ts-mocha) is
342shown in folder [ts-demo](ts-demo)
343
344### CoffeeScript
345
346CoffeeScript example is in [coffee-demo](coffee-demo) folder. Watch mode is
347working properly.
348
349## Inspiration
350
351Came during WorkBar Cambridge Happy Hour on the terrace as I was thinking about
352difficulty of adding CoffeeScript / TypeScript support to
353[snap-shot][snap-shot] project. Got the idea of overriding `global.it` when
354loading `snap-shot` because a day before I wrote [repeat-it][repeat-it]
355which overrides it and it is very simple [repeat/src/index.js][repeat source].
356
357[snap-shot]: https://github.com/bahmutov/snap-shot
358[repeat-it]: https://github.com/bahmutov/repeat-it
359[repeat source]: https://github.com/bahmutov/repeat-it/blob/master/src/index.js
360
361## Related projects
362
363This NPM module is part of my experiments with snapshot testing. There are
364lots of other ones, blog posts and slides on this topic.
365
366* [snap-shot-core][snap-shot-core] implements loading and saving snapshots
367* [snap-shot](https://github.com/bahmutov/snap-shot) is an alternative to this
368 package that tries to determine spec name using stack trace and static
369 source code parsing. Hard to do for transpiled code!
370* [schema-shot](https://github.com/bahmutov/schema-shot) is
371 "schema by example" snapshot testing
372* [subset-shot](https://github.com/bahmutov/subset-shot)
373 where new value can be a superset of the saved snapshot
374* Blog post [Picking snapshot library](https://glebbahmutov.com/blog/picking-snapshot-library/)
375* Slides [Snapshot testing the hard way](https://slides.com/bahmutov/snapshot-testing-the-hard-way)
376
377[snap-shot-core]: https://github.com/bahmutov/snap-shot-core
378
379### Small print
380
381Author: Gleb Bahmutov <gleb.bahmutov@gmail.com> © 2017
382
383* [@bahmutov](https://twitter.com/bahmutov)
384* [glebbahmutov.com](https://glebbahmutov.com)
385* [blog](https://glebbahmutov.com/blog)
386
387License: MIT - do anything with the code, but don't blame me if it does not work.
388
389Support: if you find any problems with this module, email / tweet /
390[open issue](https://github.com/bahmutov/snap-shot-it/issues) on Github
391
392## MIT License
393
394Copyright (c) 2017 Gleb Bahmutov <gleb.bahmutov@gmail.com>
395
396Permission is hereby granted, free of charge, to any person
397obtaining a copy of this software and associated documentation
398files (the "Software"), to deal in the Software without
399restriction, including without limitation the rights to use,
400copy, modify, merge, publish, distribute, sublicense, and/or sell
401copies of the Software, and to permit persons to whom the
402Software is furnished to do so, subject to the following
403conditions:
404
405The above copyright notice and this permission notice shall be
406included in all copies or substantial portions of the Software.
407
408THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
409EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
410OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
411NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
412HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
413WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
414FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
415OTHER DEALINGS IN THE SOFTWARE.
416
417[npm-icon]: https://nodei.co/npm/snap-shot-it.svg?downloads=true
418[npm-url]: https://npmjs.org/package/snap-shot-it
419[ci-image]: https://travis-ci.org/bahmutov/snap-shot-it.svg?branch=master
420[ci-url]: https://travis-ci.org/bahmutov/snap-shot-it
421[semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
422[semantic-url]: https://github.com/semantic-release/semantic-release
423[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg
424[standard-url]: http://standardjs.com/