1 | # util.fixture
|
2 |
|
3 | > Test fixture library
|
4 |
|
5 | [![build](https://circleci.com/gh/jmquigley/util.fixture/tree/master.svg?style=shield)](https://circleci.com/gh/jmquigley/util.fixture/tree/master)
|
6 | [![analysis](https://img.shields.io/badge/analysis-tslint-9cf.svg)](https://palantir.github.io/tslint/)
|
7 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
|
8 | [![testing](https://img.shields.io/badge/testing-jest-blue.svg)](https://facebook.github.io/jest/)
|
9 | [![NPM](https://img.shields.io/npm/v/util.fixture.svg)](https://www.npmjs.com/package/util.fixture)
|
10 | [![coverage](https://coveralls.io/repos/github/jmquigley/util.fixture/badge.svg?branch=master)](https://coveralls.io/github/jmquigley/util.fixture?branch=master)
|
11 |
|
12 | A testing fixture class used to simplify managing testing artifacts. A testing fixture in this context is set of files and directories that are copied/loaded into a temporary location, named template values within the files are replaced, the fixture is used for that test, and then it is destroyed when all tests are complete. The constructor looks for fixtures from the root of the project in `./test/fixtures`. This directory contains a set of additional directories. Each subdirectory represents a named fixture that can be used in a test. e.g.
|
13 |
|
14 | ./__tests__/fixtures/some-test
|
15 |
|
16 | This would contain a usable fixture named `some-test`. The name of the fixture is a parameter to the constructor. Within these named fixture directories one can place files and directories used by a test. See the usage section below on how to use this within a test.
|
17 |
|
18 | The reason for this module is to deal with concurrency in the [ava] test runner. It runs tests concurrently, so using one directory for test fixtures is a problem as the tests will share artifacts incorrectly (think of two tests trying to access the same file and writing different things at the same time). This overcomes that issue by making a separate temporary location each time a fixture is instantiated; different tests will each have their own copy of the fixture.
|
19 |
|
20 | #### Features
|
21 | - Template replacement
|
22 | - Automatic parsing of a JSON file within the fixture
|
23 | - Parsing of a data file list
|
24 | - Can be used with concurrent test processing
|
25 | - Execution of a fixture.js script during instantiation
|
26 | - Lorem Ipsum and Pattern generators
|
27 |
|
28 | ## Installation
|
29 |
|
30 | This module uses [yarn](https://yarnpkg.com/en/) to manage dependencies and run scripts for development.
|
31 |
|
32 | To install as an application dependency with cli:
|
33 | ```
|
34 | $ yarn add --dev util.fixture
|
35 | ```
|
36 |
|
37 | To build the app and run all tests:
|
38 | ```
|
39 | $ yarn run all
|
40 | ```
|
41 |
|
42 | ## Usage
|
43 |
|
44 | #### Simple Fixture
|
45 | ```javascript
|
46 | const Fixture = require('util.fixture');
|
47 |
|
48 | let fixture = new Fixture('test-fixture-1');
|
49 | let s = fixture.read('somefile.txt');
|
50 |
|
51 | ... // your test
|
52 | ```
|
53 |
|
54 | This will copy the contents of the named fixture `test-fixture-1` to a temporary location. The temporary directory location is based on the environment variables `TMP` or `TEMP`. If neither of these are set, then the directory `~/.tmp` is chosen (and created if it doesn't exist). This is the home directory of the user running the test. Within this directory another directory is created named `unit-test-data`. All test fixtures are copied to this location when used. The name of each fixture is a generated UUID to make them unique for each test each time it is exectued. When a new fixture is created it returns an object with attributes related to that fixture (the *attributes* are listed below). The structure of the fixture could be:
|
55 |
|
56 | ```
|
57 | ./__tests__/fixtures/test-fixture-1/
|
58 | somefile.txt
|
59 | somedirectory/
|
60 | ...
|
61 | ```
|
62 |
|
63 | In this example the temporary location `fixture.dir` represents the temporary directory where the fixture was copied and expanded. From this location one would see the directory/file structure above. The creation of the fixture also results in template replacement. In this example there are no custom templates variables; only builtins (template replacement will be explained below).
|
64 |
|
65 | This example also shows that a file named `somefile.txt` was read from the fixture into a temporary string variable `s` using the `read()`.
|
66 |
|
67 |
|
68 | #### Simple JSON
|
69 | ```javascript
|
70 | const Fixture = require('util.fixture');
|
71 |
|
72 | let fixture = new Fixture('simple-json');
|
73 |
|
74 | ... // your test
|
75 | ```
|
76 |
|
77 | Similar to a simple fixture above. If the fixture contains a file with the name `obj.json` then it will load the fixture with this JSON file, parse this JSON, perform template replacement, and save it within the fixture in an exposed field named `fixture.obj` and `fixture.jsonObj` (these will reference the same object). The structure of the fixture would be:
|
78 |
|
79 | ```
|
80 | ./__tests__/fixtures/simple-json/
|
81 | obj.json
|
82 | somedirectory/
|
83 | ...
|
84 | ```
|
85 |
|
86 | One can see the contents of the JSON file with the property `fixture.json`. This is the string data read from the given JSON file.
|
87 |
|
88 |
|
89 | #### JSON with Template Replacement
|
90 | ```javascript
|
91 | const Fixture = require('util.fixture');
|
92 |
|
93 | let fixture = new Fixture('some-fixture', {
|
94 | templateData: {
|
95 | replaceMe: 'test data'
|
96 | }
|
97 | });
|
98 |
|
99 | ... // your test
|
100 | ```
|
101 |
|
102 | Loads a JSON file saved in a fixture location and replaces custom text strings using template replacement. This would search the given JSON file for all instances of the string `{replaceMe}` and substitute the given value in the template (`test data`). An example JSON in this case would be:
|
103 |
|
104 | ```json
|
105 | {
|
106 | "testData": "{replaceMe}",
|
107 | "testBool": true
|
108 | }
|
109 | ```
|
110 |
|
111 | resulting in:
|
112 |
|
113 | ```json
|
114 | {
|
115 | "testData": "test data",
|
116 | "testBool": true
|
117 | }
|
118 | ```
|
119 |
|
120 | It will lead to a fixture object returned like the previous two examples.
|
121 |
|
122 | #### Simple YAML
|
123 | The fixture will also process YAML files inside of a fixture. If the fixture contains a file with the name `obj.yaml` then it will load the fixture with this YAML file, parse it, performan template replacement, and save it within the fixture in an exposed filed named `fixture.yamlObj`. Note that `fixture.obj` is the legacy name for JSON objects and NOT YAML. The fixture will also contain a property named `fixture.yaml` which is a string representing the contents of the YAML file.
|
124 |
|
125 |
|
126 | #### Fixture with Template Replacement
|
127 | ```javascript
|
128 | const Fixture = require('util.fixture');
|
129 |
|
130 | let fixture = Fixture('test-fixture', {
|
131 | jsonFile: 'test-directory/somefile.json',
|
132 | dataFile: 'test-file.txt',
|
133 | templateData: {
|
134 | replaceMe: 'test data'
|
135 | }
|
136 | });
|
137 |
|
138 | ... // your test
|
139 | ```
|
140 |
|
141 | Loads a fixture and then searches through all of the files in that fixture for template replacement values. The same replacements are applied to all files. This example also demonstrates the use of optional parameters to change the names of the JSON file and data file name. An example directory structure for `test-fixture` within the temporary location would be:
|
142 |
|
143 | ```
|
144 | ./__tests__/fixtures/test-fixture/
|
145 | test-directory/
|
146 | somefile.json
|
147 | test-file.txt
|
148 | ```
|
149 |
|
150 | The constructor would copy the fixture to the temporary location, perform template replacement on each of the files (ignoring directories), save them to their temporary versions, and then parse the `jsonFile` and `dataFile` parameters into `fixture.obj` and `fixture.data`. The example text file above named `test-file.txt` would be:
|
151 |
|
152 | ```
|
153 | Test information
|
154 |
|
155 | {replaceMe}
|
156 | ```
|
157 |
|
158 | And after replacement would be:
|
159 |
|
160 | ```
|
161 | Test information
|
162 |
|
163 | test data
|
164 | ```
|
165 |
|
166 | This sample file would also be [parsed as a file list](https://www.npmjs.com/package/util.filelist). This would read each line from the file (ignoring blank lines and # comments) and save each line into an array named `fixture.data`. This is a way to take a large list of data, parse it, and store it into an array.
|
167 |
|
168 |
|
169 | #### Empty Fixture (Temporary Directory)
|
170 | ```javascript
|
171 | const Fixture = require('util.fixture');
|
172 |
|
173 | let fixture = Fixture('tmpdir');
|
174 |
|
175 | ... // your test
|
176 | ```
|
177 |
|
178 | A fixture with the name `tmpdir` is a special case. This will create a temporary directory, but will have no files or directories within it. It will not perform any template replacements. The directory is accessible through `fixture.dir`.
|
179 |
|
180 | A temporary directory can also be created with an empty constructor:
|
181 |
|
182 | ```javascript
|
183 | const Fixture = require('util.fixture');
|
184 |
|
185 | let fixture = Fixture();
|
186 |
|
187 | ... // your test
|
188 | ```
|
189 |
|
190 | When the fixture is cleaned up this directory would be removed.
|
191 |
|
192 |
|
193 | #### Pattern Generator
|
194 | When the fixture is instatiated a test pattern string is created. This string represents a 2D array of repeated chevrons. Note that this data is stored as a newline delimited string.
|
195 |
|
196 | The default pattern is a set of chevrons of the letters `a` - `z`, where each letter is a row of 80 columns. e.g.
|
197 |
|
198 | ```
|
199 | aaaaaaaaaa...
|
200 | bbbbbbbbbb...
|
201 | cccccccccc...
|
202 | ...
|
203 | zzzzzzzzzz...
|
204 | ```
|
205 |
|
206 | To create a default pattern use:
|
207 |
|
208 | ```javascript
|
209 | const fixture = new Fixture('pattern');
|
210 | fixture.pattern; // a string representing the pattern.
|
211 | ```
|
212 |
|
213 | To create a custom pattern use:
|
214 |
|
215 | ```javascript
|
216 | const fixture = new Fixture('pattern', {
|
217 | pattern: {
|
218 | chevrons: ['0', '1', '2'],
|
219 | columns: 80,
|
220 | repeat: 5
|
221 | }
|
222 | });
|
223 | ```
|
224 | This will create a 15 x 80 grid of numbers, where each row is `0, 1, 2, ...` (15 rows).
|
225 |
|
226 |
|
227 | #### LoremIpsum Generator
|
228 | When the `Fixture` is instantiated it will generate a [lorem ipsum](https://en.wikipedia.org/wiki/Lorem_ipsum) string (random text). It uses the [lorem-ipsum](https://github.com/knicklabs/lorem-ipsum.js) package to create the text. The text is accessed through the property `.loremIpsum`. See the package link above for configuration options. A special name `loremIpsum` can be used if no fixture directory structure is required.
|
229 |
|
230 | ```javascript
|
231 | const fixture = new Fixture('loremIpsum', {
|
232 | loremIpsum: {
|
233 | count: 3,
|
234 | sentenceLowerBound: 10,
|
235 | sentenceUpperBound: 20
|
236 | }
|
237 | });
|
238 |
|
239 | fixture.loremIpsum;
|
240 | ```
|
241 |
|
242 | The fixture above generates a custom lorem ipsum string with 3 sentences where each sentence has between 10 and 20 words.
|
243 |
|
244 |
|
245 | #### Script Execution
|
246 | If the fixture contains a script named `fixture.js` then this script will be executed when the fixture is instantiated. Note that this script will only work with built in node *requires* unless the fixture itself contains its own `node_modules` directory. The script is removed from the temporary fixture after it is executed.
|
247 |
|
248 | The name of the execution script can be changed as an optional parameter to the fixture construtor:
|
249 |
|
250 | ```javascript
|
251 | const Fixture = require('util.fixture');
|
252 |
|
253 | let fixture = Fixture('test-fixture', {
|
254 | script: 'ascript.js'
|
255 | });
|
256 |
|
257 | ... // your test
|
258 | ```
|
259 |
|
260 |
|
261 | #### Cleanup
|
262 | When all tests are complete the fixture should be cleaned up. The class contains a static method named `cleanup`. In [ava] this is used in the `test.after.always` hook:
|
263 |
|
264 | ```javascript
|
265 | test.after.always.cb(t => {
|
266 | Fixture.cleanup((err: Error, directories: string[]) => {
|
267 | if (err) {
|
268 | t.fail(err.message);
|
269 | }
|
270 |
|
271 | directories.forEach((directory: string) => {
|
272 | t.false(fs.existsSync(directory));
|
273 | });
|
274 |
|
275 | t.end();
|
276 | });
|
277 | });
|
278 | ```
|
279 |
|
280 | The cleanup function only needs to be called once per testing file. The class keeps track of all test directories that were created when the Fixture is instantiated and removes them when the cleanup is called.
|
281 |
|
282 | Note that when using [ava] the hook `test.after.always` is executed within each separate test file and NOT once per overall test execution. This code needs to be part of each test file to clean up the files related to those tests. If this is not executed, then all of the temp files created by the fixture will remain (and you will be required to clean them up). Note that this process has intermittent issues on Windows. It seems that the [fs-extra remove function](https://github.com/jprichardson/node-fs-extra/blob/master/docs/remove.md) will occasionally fail when trying to delete files if Windows still has a process attached to it (like file explorer).
|
283 |
|
284 |
|
285 | See [tests.js](https://github.com/jmquigley/util.fixture/blob/master/test/tests.ts) in this repository for examples of these usage patterns.
|
286 |
|
287 | The module also contains a helper function named `cleanup()` which handles the process listed above. The example below works with [jest](https://jestjs.io/) testing.
|
288 |
|
289 | ```javascript
|
290 | import {cleanup, Fixture} from "util.fixture";
|
291 |
|
292 | afterall((done) => {
|
293 | cleanup({done, message: "done with fixture testing"});
|
294 | });
|
295 | ```
|
296 |
|
297 |
|
298 | ## API
|
299 |
|
300 | ### Fixture([{name}, opts])
|
301 |
|
302 | This is a single constructor exposed by the module.
|
303 |
|
304 | ##### parameters
|
305 |
|
306 | - `name {string}`: The name of the fixture to use. This corresponds to an entry in `./__tests__/fixtures/{name}`. When this is empty an empty, temporary directory is created.
|
307 | - `opts: {object}`: optional parameters (listed below)
|
308 |
|
309 | ##### options
|
310 |
|
311 | The following options can be used to customize the fixture. They can be set as an optional object given to the class during instantiation or within `package.json` in a section named *fixture*. The precedence of application, from lowest to highest, is the default internal options, the `package.json`, and finally the constructor options.
|
312 |
|
313 | - `basedir {string}`: The base location where the fixture will be temporarily located. The default location is determined by the environment variable `TMP` first or `TEMP` if TMP is not found. If neither of these are set, then `~/.tmp/unit-test-data` is created and used within the users home directory. This must be a directory that is writable by the user running the test.
|
314 | - `dataFile {string}`: The name of the data list file, within the fixture location, that will be parsed and saved into `fixture.data` as an array of lines. By default this file is `data.list`. It is parsed by the [util.filelist](https://www.npmjs.com/package/util.filelist) module. This is a way to get a large list of information into the fixture.
|
315 | - `fixtureDirectory {string}`: The location within the project where fixtures are found. The default is `./__tests__/fixtures`.
|
316 | - `jsonFile {string}`: The name of a JSON data file that will be parsed and saved into `fixture.obj` and `fixture.jsonObj`. By default this file is named `obj.json` within the fixture.
|
317 | - `pattern {object}` - configuration overrides for the pattern generator. It contains 3 attributes: columns, repeat count, and an array of chevrons.
|
318 | - `loremIpsum {object}`: [configuration options](https://github.com/knicklabs/lorem-ipsum.js/blob/master/README.md) for the lorem ipsum generator.
|
319 | - `script {string}`: The name of the node script that will be executed when the fixture is instantiated. The default nam is `fixture.js`.
|
320 | - `templateData {object}`: a map of key/value pairs that are used for replacement within each fixture file. The [string-template](https://www.npmjs.com/package/string-template) library is used to perform the replacement. All files are checked.
|
321 | - `yamlFile {string}`: The name of a YAML data file that will be parsed and saved into `fixture.yamlObj`. By default this file is named `obj.yaml` within the fixture.
|
322 |
|
323 |
|
324 | ##### attributes
|
325 | Instantiation of the class returns an object with the following attributes:
|
326 |
|
327 | - `.basedir` - the root temporary directory for all tests. This can be changed as an option to the function constructor
|
328 | - `.cleanup()` - static method on the class that removes the base directory and and all artifacts copied there. Generally this would be used at the end of ALL testing. In [ava] this would be done in the `test.after.always` function.
|
329 | - `.data` - if the fixture contains a file named `data.list` or a text file named by the `dataFile` option, then it will be processed and placed here. This is an Array object when defined.
|
330 | - `.dir` - the location of the temporary directory created for this fixture.
|
331 | - `.files` - an array of files that were found within the fixture and placed into the temporary `.dir`.
|
332 | - `.json` - a text string representing a JSON file that was read into the fixture. It's an empty string if there was no JSON file to process.
|
333 | - `.jsonObj` - if the fixture contains `obj.json` or a JSON file named by the `jsonFile` option, then it is parsed and the contents of that JSON are stored here. The JSON file will go through template replacement before it is parsed. There is also an alias named `.obj`.
|
334 | - `.name` - the name of the requested fixture. This is the first string parameter to the Fixture constructor.
|
335 | - `.loremIpsum` - a string containing a randomly generated lorem ipsum string/sentence.
|
336 | - `.pattern` - a string contained a MxN repeating pattern.
|
337 | - `.read({filename}): string` - Reads the contents of one of the files within the fixture and returns it as a string. The filename is a relative path within the fixture (the absolute path is resolved by the class). This is the contents of the file after it has been processed through template replacement.
|
338 | - `.src` - the absolute directory path for the fixture files.
|
339 | - `.toString()` - returns a string that shows the internal representation of the fixture. It will show all of these attributes and the options that were passed to the class when it was instantiated.
|
340 | - `.yaml` - a text string representing a YAML file that was read into the fixture. It's an empty string if there was no YAML file to process.
|
341 | - `.yamlObj` - if the fixture contains `obj.yaml` or a YAML file named by the `yamlFile` option, then it is parsed and the contents of that YAML are stored here. The YAML file will go through template replacement before it is parsed.
|
342 |
|
343 | ##### events
|
344 |
|
345 | - `loaded` - This event fires once the constructor has finished creating the fixture.
|
346 |
|
347 | ## Template Data Variables
|
348 | The following are template variables that automatically added to the variable expansion list (templateData).
|
349 |
|
350 | - `DIR`: the location where the fixture will be copied. e.g.
|
351 |
|
352 | ```
|
353 | {
|
354 | file: "{DIR}/somefile.txt"
|
355 | }
|
356 | ```
|
357 |
|
358 | [ava]: https://github.com/avajs/ava
|