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 |
|
15 | This tool makes [snapshot testing][snapshot testing] for Mocha (and other BDD)
|
16 | frameworks quick and painless. This module spies on global `it` function,
|
17 | which allows it to accurately get test information (beating static code parsing
|
18 | done in [snap-shot][snap-shot]); it should work in transpiled code.
|
19 |
|
20 | [snapshot testing]: https://glebbahmutov.com/blog/snapshot-testing/
|
21 |
|
22 | This package uses [snap-shot-compare](https://github.com/bahmutov/snap-shot-compare)
|
23 | to display object and text difference intelligently.
|
24 |
|
25 | This function also includes [data-driven][data-driven] testing mode,
|
26 | similar to [sazerac][sazerac], see [Data-driven testing](#data-driven-testing)
|
27 | section 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 |
|
34 | Requires [Node](https://nodejs.org/en/) version 4 or above.
|
35 |
|
36 | ```sh
|
37 | npm install --save-dev snap-shot-it
|
38 | ```
|
39 |
|
40 | ## Use
|
41 |
|
42 | Example from [spec.js](src/spec.js)
|
43 |
|
44 | ```js
|
45 | const snapshot = require('snap-shot-it')
|
46 | describe('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 |
|
55 | Run Mocha tests, then open the created
|
56 | [__snapshots__/spec.js](__snapshots__/spec.js) file
|
57 |
|
58 | ```js
|
59 | exports['example works 1'] = 30
|
60 |
|
61 | exports['example works 2'] = "a text message"
|
62 |
|
63 | exports['example works 3'] = 42
|
64 | ```
|
65 |
|
66 | Suppose you change the resolved value from `42` to `80`
|
67 |
|
68 | ```js
|
69 | const snapshot = require('snap-shot-it')
|
70 | describe('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 |
|
79 | The test will fail
|
80 |
|
81 | ```
|
82 | 1) example works:
|
83 | Error: 42 !== 80
|
84 | ```
|
85 |
|
86 | The error message should intelligently handle numbers, objects, arrays,
|
87 | multi-line text, etc.
|
88 |
|
89 | ## Returned value
|
90 |
|
91 | The 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
|
94 | const out = snapshot('my name', 42)
|
95 | // {value: 42, key: 'my name'}
|
96 | ```
|
97 |
|
98 | ## Advanced use
|
99 |
|
100 | You can see the saves snapshot values by running with environment variable
|
101 |
|
102 | ```bash
|
103 | SNAPSHOT_SHOW=1 npm test
|
104 | ```
|
105 |
|
106 | You can see snapshot values without writing them into the snapshot file
|
107 |
|
108 | ```bash
|
109 | SNAPSHOT_DRY=1 npm test
|
110 | ```
|
111 |
|
112 | You can update snapshot values
|
113 |
|
114 | ```bash
|
115 | SNAPSHOT_UPDATE=1 npm test
|
116 | ```
|
117 |
|
118 | You can use the following aliases: `SNAPSHOT_UPDATE=1`, `SNAPSHOTS_UPDATE=1` or `SNAP_SHOT_UPDATE=1`.
|
119 |
|
120 | ## Sorted snapshots
|
121 |
|
122 | If you want to sort saved snapshots alphabetically inside each snapshot file, run with
|
123 |
|
124 | ```bash
|
125 | SNAPSHOT_SORT=1 npm test
|
126 | ```
|
127 |
|
128 | You 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 |
|
140 | Hopefully sorting snapshots would help when updating them.
|
141 |
|
142 | ## Named snapshots
|
143 |
|
144 | Renaming tests might lead to confusion and pruning snapshots. You can name the snapshots
|
145 | yourself
|
146 |
|
147 | ```js
|
148 | const value = 42
|
149 | snapshot('my name', value)
|
150 | ```
|
151 |
|
152 | The snapshots will be saved as
|
153 |
|
154 | ```js
|
155 | exports['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 |
|
162 | If 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
|
165 | snapshot('my shared snapshot', value, { allowSharedSnapshot : true })
|
166 | // some time later
|
167 | snapshot('my shared snapshot', value, { allowSharedSnapshot : true })
|
168 | ```
|
169 |
|
170 | ## Pruning
|
171 |
|
172 | If 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
|
175 | SNAPSHOT_SKIP_PRUNING=1 npm test
|
176 | ```
|
177 |
|
178 | ## Data-driven testing
|
179 |
|
180 | Writing multiple input / output pairs for a function under test quickly
|
181 | becomes tedious. Luckily, you can test a function by providing multiple
|
182 | inputs and a single snapshot of function's *behavior* will be saved.
|
183 |
|
184 | ```js
|
185 | // checks if n is prime
|
186 | const isPrime = n => ...
|
187 | it('tests prime', () => {
|
188 | snapshot(isPrime, 1, 2, 3, 4, 5, 6, 7, 8, 9)
|
189 | })
|
190 | ```
|
191 |
|
192 | The saved snapshot file will have clear mapping between given input and
|
193 | produced result
|
194 |
|
195 | ```js
|
196 | // snapshot file
|
197 | exports['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 |
|
225 | You can also test functions that expect multiple arguments by providing
|
226 | arrays of inputs.
|
227 |
|
228 | ```js
|
229 | const add = (a, b) => a + b
|
230 | it('checks behavior of binary function add', () => {
|
231 | snapshot(add, [1, 2], [2, 2], [-5, 5], [10, 11])
|
232 | })
|
233 | ```
|
234 |
|
235 | Again, the snapshot file gives clear picture of the `add` behavior
|
236 |
|
237 | ```js
|
238 | // snapshot file
|
239 | exports['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 |
|
274 | See [src/data-driven-spec.js](src/data-driven-spec.js) for more examples.
|
275 |
|
276 | ## Debugging
|
277 |
|
278 | Run with environment variable `DEBUG=snap-shot-it ...` to see log messages.
|
279 | Because under the hood it uses [snap-shot-core][snap-shot-core] you might
|
280 | want to show messages from both libraries with `DEBUG=snap-shot* ...`
|
281 |
|
282 | ## Data callbacks
|
283 |
|
284 | You 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 |
|
298 | Each 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 |
|
306 | By 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 |
|
318 | input spec files
|
319 |
|
320 | ```
|
321 | specs/
|
322 | spec.js
|
323 | subfolder/
|
324 | spec2.js
|
325 | ```
|
326 |
|
327 | result should be
|
328 |
|
329 | ```
|
330 | __snapshots__/
|
331 | specs/
|
332 | spec.js
|
333 | subfolder/
|
334 | spec2.js
|
335 | ```
|
336 |
|
337 | ## Examples
|
338 |
|
339 | ### TypeScript
|
340 |
|
341 | An example using [ts-mocha](https://github.com/piotrwitek/ts-mocha) is
|
342 | shown in folder [ts-demo](ts-demo)
|
343 |
|
344 | ### CoffeeScript
|
345 |
|
346 | CoffeeScript example is in [coffee-demo](coffee-demo) folder. Watch mode is
|
347 | working properly.
|
348 |
|
349 | ## Inspiration
|
350 |
|
351 | Came during WorkBar Cambridge Happy Hour on the terrace as I was thinking about
|
352 | difficulty of adding CoffeeScript / TypeScript support to
|
353 | [snap-shot][snap-shot] project. Got the idea of overriding `global.it` when
|
354 | loading `snap-shot` because a day before I wrote [repeat-it][repeat-it]
|
355 | which 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 |
|
363 | This NPM module is part of my experiments with snapshot testing. There are
|
364 | lots 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 |
|
381 | Author: 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 |
|
387 | License: MIT - do anything with the code, but don't blame me if it does not work.
|
388 |
|
389 | Support: 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 |
|
394 | Copyright (c) 2017 Gleb Bahmutov <gleb.bahmutov@gmail.com>
|
395 |
|
396 | Permission is hereby granted, free of charge, to any person
|
397 | obtaining a copy of this software and associated documentation
|
398 | files (the "Software"), to deal in the Software without
|
399 | restriction, including without limitation the rights to use,
|
400 | copy, modify, merge, publish, distribute, sublicense, and/or sell
|
401 | copies of the Software, and to permit persons to whom the
|
402 | Software is furnished to do so, subject to the following
|
403 | conditions:
|
404 |
|
405 | The above copyright notice and this permission notice shall be
|
406 | included in all copies or substantial portions of the Software.
|
407 |
|
408 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
409 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
410 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
411 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
412 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
413 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
414 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
415 | OTHER 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/
|