Skip to content

Commit

Permalink
Merge pull request #5 from MarcL/day4
Browse files Browse the repository at this point in the history
Day 4 : Time Travel with Fake Timers
  • Loading branch information
MarcL authored Aug 27, 2017
2 parents 503edbe + b22abd6 commit 3ed1f4d
Show file tree
Hide file tree
Showing 5 changed files with 288 additions and 6 deletions.
191 changes: 191 additions & 0 deletions doc/day4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# Day 4 notes - Time travel with fake timers

## Speed up yesterday's asynchronous tests

### Copy day 3 source code and first test

Make a copy of the code from day3 but we only testing our slow `setTimeout` code and not the promise. **Note:** don't export as a default ES6 module as we're going to export a couple of functions

```javascript
function timeout(callback) {
setTimeout(() => callback('hello'), 1000);
}

export {
timeout
};
```

Impor the function in your test code.

```javascript
import {expect} from 'chai';
import {timeout} from '../src/day4';

describe('day 4 tests', () => {
it('should return expected value from callback', (done) => {
timeout((returnedData) => {
expect(returnedData).to.equal('hello');
done();
});
});
});
```

If you run the code you'll see that it still runs in just over 1000ms.

```shell
mocha --require babel-register test/day4.test.js
```

You're going to install `sinon` which is a library of test double functions. It has a special fake timer implementation which makes asynchronous time-related functions become syncronous and controllable.

```shell
npm install --save-dev sinon
```

You can now update the test to use the fake timer.

```javascript
describe('timeout tests', () => {
let clock;
beforeEach(() => {
// Set the fake timer before all of our tests
clock = sinon.useFakeTimers();
});

afterEach(() => {
// Restore back to async time/date functions after each test
clock.restore();
});

it('should return expected value from callback', (done) => {
timeout((returnedData) => {
expect(returnedData).to.equal('hello');
done();
});

// You need to tick the clock by the amount you need.
// Here it's 1000ms or 1 second to allow the timeout callback to trigger
clock.tick(1000);
});
});
```

Try the test again and you'll see that it runs in about 150ms! A great improvement and the sort of speed you want from unit tests.

## DateDescriber - faking dates

Let's add a new function which will take a date and tell us whether the year is in the future, the past or the current year.

```javascript
// Under timeout code

function dateDescriber(otherDate) {
}

export {
timeout,
dateDescriber
};
```

Create a new test `describe` block and add a test to check that the date is in the future.

```javascript
describe('dateDescriber tests', () => {
it('should correctly describe a future year', () => {
const description = dateDescriber(
new Date(2018, 1, 1)
);

expect(description).to.equal('in the future');
});

it('should correctly describe a past year', () => {
const description = dateDescriber(
new Date(2016, 1, 1)
);

expect(description).to.equal('in the past');
});

it('should correctly describe current year', () => {
const description = dateDescriber(
new Date(2017, 1, 1)
);

expect(description).to.equal('this year');
});
});
```

If you run the tests, they will fail so let's add the code to make them all pass:

```javascript
function dateDescriber(otherDate) {
const dateNow = new Date();

const otherYear = otherDate.getFullYear();
const currentYear = dateNow.getFullYear();

const yearDifference = otherYear - currentYear;

if (yearDifference > 0) {
return 'in the future';
} else if (yearDifference < 0) {
return 'in the past';
} else {
return 'this year';
}
}
```

Now all of the tests will pass but we have a major problem. These tests will run correctly but what happens if it's 2018? They'll start to fail. What should we do? Let's use `sinon.useFakeTimer` to set the initial date to what we would like it to be. Add a `beforeEach` and `afterEach` function to the `describe` block and let's set the initial date to January 1 2017.

```javascript
describe('dateDescriber tests', () => {
let clock;
const currentYear = 2017;

beforeEach(() => {
const now = new Date(currentYear, 1, 1);
clock = sinon.useFakeTimers(now);
});

afterEach(() => {
clock.restore();
});

// Other code snipped
```
Great! The tests now pass and we've set the initial date. However, notice that we're still hardcoding dates into our tests. If we change `currentYear` to 2018, our future test will still fail. It would be better to make them relative to our `currentYear` variable. Let's tidy up our tests so we're know they're always correct.
```javascript
it('should correctly describe a future year', () => {
const description = dateDescriber(
new Date(currentYear + 1, 1, 1)
);

expect(description).to.equal('in the future');
});

it('should correctly describe a past year', () => {
const description = dateDescriber(
new Date(currentYear - 1, 1, 1)
);

expect(description).to.equal('in the past');
});

it('should correctly describe current year', () => {
const description = dateDescriber(
new Date(currentYear, 1, 1)
);

expect(description).to.equal('this year');
});
```
Now you should be happy with your tests. They all pass and the dates won't alter if you run them on a different environment or in the future. Great work!
11 changes: 6 additions & 5 deletions doc/notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@

## Day 4 - Time travel with fake timers

1. Install sinon
2. Fake timer in test
3. Use mocha to stub and restore timers
4. Date code which displays different strings based on date
5. Set fake date to now and tick to the future
1. Speed up slow tests
2. Use a fake timer
3. Use sinon to stub and restore timers
4. mocha beforeEach / afterEach
5. Date describer - tell us if the date is in the future
6. Fake the current time

## Day 5 - GitHub stargazing with stubs & spies

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"babel-preset-env": "~1.6.0",
"babel-register": "~6.24.1",
"chai": "~4.1.1",
"mocha": "~3.5.0"
"mocha": "~3.5.0",
"sinon": "~3.2.1"
}
}
26 changes: 26 additions & 0 deletions src/day4.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
function timeout(callback) {
setTimeout(() => callback('hello'), 1000);
}

function dateDescriber(otherDate) {
const dateNow = new Date();

const otherYear = otherDate.getFullYear();
const currentYear = dateNow.getFullYear();

const yearDifference = otherYear - currentYear;

if (yearDifference > 0) {
return 'in the future';
} else if (yearDifference < 0) {
return 'in the past';
} else {
return 'this year';
}
}

export {
timeout,
dateDescriber
};

63 changes: 63 additions & 0 deletions test/day4.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import sinon from 'sinon';
import {expect} from 'chai';
import {timeout, dateDescriber} from '../src/day4';

describe('day 4 tests', () => {
describe('timeout tests', () => {
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers();
});

afterEach(() => {
clock.restore();
});

it('should return expected value from callback', (done) => {
timeout((returnedData) => {
expect(returnedData).to.equal('hello');
done();
});

clock.tick(1000);
});
});

describe('dateDescriber tests', () => {
let clock;
const currentYear = 2017;

beforeEach(() => {
const now = new Date(currentYear, 1, 1);
clock = sinon.useFakeTimers(now);
});

afterEach(() => {
clock.restore();
});

it('should correctly describe a future year', () => {
const description = dateDescriber(
new Date(currentYear + 1, 1, 1)
);

expect(description).to.equal('in the future');
});

it('should correctly describe a past year', () => {
const description = dateDescriber(
new Date(currentYear - 1, 1, 1)
);

expect(description).to.equal('in the past');
});

it('should correctly describe current year', () => {
const description = dateDescriber(
new Date(currentYear, 1, 1)
);

expect(description).to.equal('this year');
});
});
});

0 comments on commit 3ed1f4d

Please sign in to comment.