|
| 1 | +--- |
| 2 | +title: Testing |
| 3 | +weight: 20 |
| 4 | +--- |
| 5 | + |
| 6 | +{{< toc >}} |
| 7 | + |
| 8 | +Local testing for Eask is done using the [Jest]() testing framework. |
| 9 | + |
| 10 | +A custom `TestContext` class is used to manage the execution environment for each test suite. |
| 11 | + |
| 12 | +### Running Tests |
| 13 | + |
| 14 | +If you have not done so already, run `npm install --dev` |
| 15 | + |
| 16 | +Always run from the project root (i.e. same directory as `package.json`) |
| 17 | + |
| 18 | +- run all tests `npm run test` |
| 19 | +- run a single test `npm run test path/to/test.js` |
| 20 | +- run tests with full output `npm run test-debug` |
| 21 | +- remove files created during test `npm run test-reset` |
| 22 | + |
| 23 | +### Environment Vars |
| 24 | + |
| 25 | +| Name | Type | Default | Meaning | |
| 26 | +|:---------------|:-------|---------|:-------------------------------------------------------------------------------------------| |
| 27 | +| `ALLOW_UNSAFE` | bool | 0 | Run tests in `testUnsafe` blocks. These can affect your emacs config or settings. | |
| 28 | +| `DEBUG` | bool | 0 | Print full output from commands in test. | |
| 29 | +| `EASK_COMMAND` | path | "eask" | Path to Eask. Usually either `eask` or `./bin/eask` (include local changes). | |
| 30 | +| `TIMEOUT` | number | 25000 | Command timeout in ms. Note this is different than Jest's timeout, which should be larger. | |
| 31 | + |
| 32 | +### How to Write a Test |
| 33 | + |
| 34 | +**Folder structure** |
| 35 | + |
| 36 | +Tests should be in `test/js` |
| 37 | +Related tests should be in the same file with the suffix `.test.js`. |
| 38 | + |
| 39 | +If the test needs some specific project files, put them in a new folder within `test/js` |
| 40 | +For example, files in `test/js/foo` would be expected to be for `foo.test.js`. |
| 41 | + |
| 42 | +The exception is `test/js/empty`, which is simply an empty folder. |
| 43 | +If you use it, make sure to run `eask clean all` before your tests. |
| 44 | + |
| 45 | +**Test File structure** |
| 46 | + |
| 47 | +``` javascript |
| 48 | +const { TestContext } = require("./helpers"); |
| 49 | + |
| 50 | +describe("emacs", () => { |
| 51 | + const ctx = new TestContext("./test-js/empty"); |
| 52 | + |
| 53 | + beforeAll(async () => await ctx.runEask("clean all")); |
| 54 | + afterAll(() => ctx.cleanUp); |
| 55 | + |
| 56 | + test("eask emacs --version", async () => { |
| 57 | + await ctx.runEask("emacs --version"); |
| 58 | + }); |
| 59 | + |
| 60 | + test("eask emacs --batch --eval", async () => { |
| 61 | + await ctx.runEask( |
| 62 | + 'emacs --batch --eval "(require (quote ert))" --eval "(ert-deftest mytest () (should-not (display-graphic-p)))" -f ert-run-tests-batch', |
| 63 | + ); |
| 64 | + }); |
| 65 | +}); |
| 66 | +``` |
| 67 | + |
| 68 | +In Jest, group related tests using `describe`. Tests in the same describe block can share setup/teardown code, can be disabled as a group and are grouped under the same heading in output. |
| 69 | + |
| 70 | +Describe blocks can be nested, it's a good idea to add a nested `describe` when tests run in different directories. |
| 71 | + |
| 72 | +For each test directory you should create a new `TestContext` object. All `runEask` commands will use the `TestContext`'s working directory. |
| 73 | + |
| 74 | +You can also use `TestContext.cleanUp()` to abort any still-running commands that were called in that context. |
| 75 | + |
| 76 | +Jest's tests are in `test` blocks. Note that `it` is an alias for `test`. |
| 77 | + |
| 78 | +The `expect` API matches values in different ways and usually prints a diff as part of the failure report. |
| 79 | +See Jest's [expect()](https://jestjs.io/docs/expect) API for more info. |
| 80 | + |
| 81 | +Errors thrown in a `test` block will fail it and report the error. |
| 82 | +That's why many tests don't have an `expect` call, they simply check that the command succeeds. |
| 83 | + |
| 84 | +### Patterns |
| 85 | + |
| 86 | +Here are some common patterns for testing commands. |
| 87 | +Each of these assumes that `ctx` is a `TestContext` object. |
| 88 | + |
| 89 | +Check a command succeeds: |
| 90 | +``` javascript |
| 91 | +test("eask analyze", async () => { |
| 92 | + await ctx.runEask("analyze"); |
| 93 | +}); |
| 94 | +``` |
| 95 | + |
| 96 | +Check a command fails: |
| 97 | +``` javascript |
| 98 | +test("eask analyze", async () => { |
| 99 | + await expect(ctx.runEask("analyze")).rejects.toThrow(); |
| 100 | +}); |
| 101 | +``` |
| 102 | + |
| 103 | +Check a command fails with a specific code: |
| 104 | +``` javascript |
| 105 | +// TODO |
| 106 | +``` |
| 107 | + |
| 108 | +Check a command produces some output: |
| 109 | +``` javascript |
| 110 | +test("eask analyze", async () => { |
| 111 | + const { stdout, stderr } = await ctx.runEask("analyze"); |
| 112 | + expect(stderr).toMatch("success"); // should apppear as a substring |
| 113 | + // If you want to check both `stderr` and `stdout`, just concatenate them |
| 114 | + expect(stdout + "/n" + stderr).toMatch("success"); |
| 115 | +}); |
| 116 | +``` |
| 117 | + |
| 118 | +Match command output against a snapshot: |
| 119 | +``` javascript |
| 120 | +test("eask analyze", async () => { |
| 121 | + const res = await ctx.runEask("analyze"); |
| 122 | + expect(res).toMatchSnapshot(); |
| 123 | +}); |
| 124 | +``` |
| 125 | + |
| 126 | +The first time you run this Jest will create a new snapshot. You should check this in to version control. |
| 127 | +If the snapshot changes, you can update the snapshot by running Jest with option `-u`, for example, |
| 128 | +`npm run test -u` will update all changed snapshots. |
| 129 | + |
| 130 | +You can also match or ignore parts of an object to avoid time-varying or local data like usernames. |
| 131 | + |
| 132 | +Commands which modify global environment, for example with `-c` or `-g` options: |
| 133 | +``` javascript |
| 134 | +const { testUnsafe } = require('./helpers'); |
| 135 | + |
| 136 | +// this will only run if ALLOW_UNSAFE is != 0 |
| 137 | +testUnsafe("global install", async () => { |
| 138 | + // this installs in ~/.eask and changes ~/Eask |
| 139 | + await ctx.runEask("install -g foo"); |
| 140 | +}); |
| 141 | +``` |
| 142 | + |
| 143 | +### Common Problems |
| 144 | + |
| 145 | +- Commands in `runEask` shouldn't include `eask`! |
| 146 | + This is an error: `ctx.runEask("eask emacs")` |
| 147 | +- When using `expect(...).rejects` it should be awaited |
| 148 | +- The folder argument to `TestContext` should be relative to project root, if it doesn't exist you may get an error `NO_ENT` |
| 149 | +- If you get an error from Jest reporting open handles, then try using `afterAll(() => ctx.cleanUp())` |
| 150 | +- There are two timeout values: one used for Jest (set in `package.json`), and one used for `node.exec`, set via env var in `./helpers.js`. |
| 151 | + The `node.exec` timeout is set lower than the Jest one, so changing timeout values for tests or by `jest.setTimeout` usually won't |
| 152 | + have an effect. Instead set the timeout on the command itself `runEask("eask emacs", { timeout: 100000 })` |
0 commit comments