Skip to content

Commit a3ad7ab

Browse files
authored
Shallow clone context (#993)
1 parent 2348c6b commit a3ad7ab

File tree

3 files changed

+133
-10
lines changed

3 files changed

+133
-10
lines changed

API.md

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ Sets up a test where:
393393
- the function can return a Promise which either resolves (success) or rejects (fails).
394394
- all other return value is ignored.
395395
- `flags` - a set of test utilities described in [Flags](#flags).
396-
396+
397397
```javascript
398398
lab.experiment('my plan', () => {
399399

@@ -418,17 +418,49 @@ The `test` function is passed a `flags` object that can be used to create notes
418418

419419
#### `context`
420420

421-
An object that is passed to `before` and `after` functions in addition to tests themselves. `context` is used to set properties inside the before function that can be used by a test function later. It is meant to reduce module level variables that are set by the `before` / `beforeEach` functions. Tests aren't able to manipulate the context object for other tests.
421+
An object that is passed to `before` and `after` functions in addition to tests themselves. `context` is used to set properties inside the before function that can be used by a test function later. It is meant to reduce module level variables that are set by the `before` / `beforeEach` functions. The context object is shallow cloned when passed to tests, as well as to child experiments, allowing you to modify it for each experiment individually without conflict through the use of `before`, `beforeEach`, `after` and `afterEach`.
422422

423423
```javascript
424-
lab.before(({ context }) => {
424+
lab.experiment('my experiment', () => {
425+
426+
lab.before(({ context }) => {
427+
428+
context.foo = 'bar';
429+
})
430+
431+
lab.test('contains context', ({ context }) => {
432+
433+
expect(context.foo).to.equal('bar');
434+
});
425435

426-
context.foo = 'bar';
427-
})
436+
lab.experiment('a nested experiment', () => {
428437

429-
lab.test('contains context', ({ context }) => {
438+
lab.before(({ context }) => {
430439

431-
expect(context.foo).to.equal('bar');
440+
context.foo = 'baz';
441+
});
442+
443+
lab.test('has the correct context', ({ context }) => {
444+
445+
expect(context.foo).to.equal('baz');
446+
// since this is a shallow clone, changes will not be carried to
447+
// future tests or experiments
448+
context.foo = 'fizzbuzz';
449+
});
450+
451+
lab.test('receives a clean context', ({ context }) => {
452+
453+
expect(context.foo).to.equal('baz');
454+
});
455+
});
456+
457+
lab.experiment('another nested experiment', () => {
458+
459+
lab.test('maintains the original context', ({ context }) => {
460+
461+
expect(context.foo).to.equal('bar');
462+
});
463+
});
432464
});
433465
```
434466

@@ -748,7 +780,7 @@ Semantics:
748780
- `$lab:coverage:push$` copies the current skip state to the top of the stack, and leaves it as the current state as well
749781
- `$lab:coverage:pop$` replaces the current skip state with the top of the stack, and removes the top of the stack
750782
- if the stack is empty, `lab` will tell you by throwing the error `"unable to pop coverage bypass stack"`
751-
783+
752784
### Excluding paths from coverage reporting
753785

754786
The `--coverage-exclude` argument can be repeated multiple times in order to add multiple paths to exclude. By default the `node_modules` and `test` directories are excluded. If you want to exclude those as well as a directory named `public` you can run lab as follows:

lib/runner.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ internals.executeExperiments = async function (experiments, state, skip, parentC
287287
!internals.experimentHasTests(experiment, state) ||
288288
(state.options.bail && state.report.failures);
289289

290-
state.currentContext = parentContext ? Hoek.clone(parentContext) : {};
290+
state.currentContext = parentContext ? Hoek.clone(parentContext, { shallow: true }) : {};
291291

292292
// Before
293293

@@ -393,7 +393,7 @@ internals.executeTests = async function (experiment, state, skip) {
393393

394394
const start = Date.now();
395395
try {
396-
test.context = Hoek.clone(state.currentContext);
396+
test.context = Hoek.clone(state.currentContext, { shallow: true });
397397
await internals.protect(test, state);
398398
}
399399
catch (ex) {

test/runner.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,6 +1812,97 @@ describe('Runner', () => {
18121812
expect(notebook.failures).to.equal(1);
18131813
});
18141814

1815+
it('passes shallow clones of context to tests', async () => {
1816+
1817+
const script = Lab.script({ schedule: false });
1818+
1819+
const contextData = {
1820+
testData: { hello: 'there' },
1821+
innerTestData: { goodbye: 'you' },
1822+
additionalData: { another: 'object' },
1823+
lateAddition: { more: 'data' }
1824+
};
1825+
1826+
script.experiment('test', () => {
1827+
1828+
let outerContext;
1829+
1830+
script.before(({ context }) => {
1831+
1832+
outerContext = context;
1833+
context.testData = contextData.testData;
1834+
});
1835+
1836+
script.test('has test context', ({ context }) => {
1837+
1838+
expect(context,'is deep equal').to.equal(outerContext);
1839+
expect(context,'is a shallow clone').to.not.shallow.equal(outerContext);
1840+
expect(context.testData,'is a reference').to.shallow.equal(contextData.testData);
1841+
context.additionalData = contextData.additionalData;
1842+
});
1843+
1844+
script.test('does not see changes to context from previous test', ({ context }) => {
1845+
1846+
expect(context,'is deep equal').to.equal(outerContext);
1847+
expect(context,'is a shallow clone').to.not.shallow.equal(outerContext);
1848+
expect(context.testData,'is a reference').to.shallow.equal(contextData.testData);
1849+
expect(context.additionalData,'ignores mutation from another test').to.not.exist();
1850+
});
1851+
1852+
script.experiment('child experiment', () => {
1853+
1854+
let innerContext;
1855+
1856+
script.before(({ context }) => {
1857+
1858+
innerContext = context;
1859+
context.testData = contextData.innerTestData;
1860+
context.additionalData = contextData.additionalData;
1861+
});
1862+
1863+
script.afterEach(({ context }) => {
1864+
1865+
context.lateAddition = contextData.lateAddition;
1866+
});
1867+
1868+
script.test('has the correct context', ({ context }) => {
1869+
1870+
expect(innerContext,'is not the same reference').to.not.shallow.equal(outerContext);
1871+
expect(context,'is deep equal').to.equal(innerContext);
1872+
expect(context,'is a shallow clone').to.not.shallow.equal(innerContext);
1873+
expect(context.testData,'is a reference to inner data').to.shallow.equal(contextData.innerTestData);
1874+
expect(context.additionalData,'is a reference').to.shallow.equal(contextData.additionalData);
1875+
expect(outerContext.testData,'has not been changed').to.shallow.equal(contextData.testData);
1876+
});
1877+
1878+
script.test('receives context changes from afterEach', ({ context }) => {
1879+
1880+
expect(innerContext,'is not the same reference').to.not.shallow.equal(outerContext);
1881+
expect(context,'is deep equal').to.equal(innerContext);
1882+
expect(context,'is a shallow clone').to.not.shallow.equal(innerContext);
1883+
expect(context.testData,'is a reference to inner data').to.shallow.equal(contextData.innerTestData);
1884+
expect(context.additionalData,'is a reference').to.shallow.equal(contextData.additionalData);
1885+
expect(context.lateAddition,'is a reference').to.shallow.equal(contextData.lateAddition);
1886+
expect(outerContext.testData,'has not been changed').to.shallow.equal(contextData.testData);
1887+
});
1888+
});
1889+
1890+
script.experiment('second child experiment', () => {
1891+
1892+
script.test('does not see mutations from a peer experiment', ({ context }) => {
1893+
1894+
expect(context,'is deep equal').to.equal(outerContext);
1895+
expect(context,'is a shallow clone').to.not.shallow.equal(outerContext);
1896+
expect(context.testData,'is a reference').to.shallow.equal(contextData.testData);
1897+
});
1898+
});
1899+
});
1900+
1901+
const notebook = await Lab.execute(script, {}, null);
1902+
expect(notebook.tests.length,'has 5 tests').to.equal(5);
1903+
expect(notebook.failures,'has 0 failures').to.equal(0);
1904+
});
1905+
18151906
it('nullifies test context on finish', async () => {
18161907

18171908
const script = Lab.script({ schedule: false });

0 commit comments

Comments
 (0)