Skip to content

Commit

Permalink
Merge pull request #6 from MarcL/day5-new
Browse files Browse the repository at this point in the history
Day 5 : GitHub Stargazing with Spies & Stubs
  • Loading branch information
MarcL committed Sep 1, 2017
2 parents 3ed1f4d + 3db1d72 commit 50fdc9f
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 1 deletion.
99 changes: 99 additions & 0 deletions doc/day5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Day 5 notes - GitHub stargazing with spies and stubs

## What you'll do

Today you're going to look at calling the GitHub API using a HTTP request

## Install request-promise

You're going to make a HTTP request to the GitHub API using `request-promise`. This is a simple HTTP request client with Promise support. Install `request-promise` (and also `request` as it is a peer dependency) using `npm`:

```shell
npm install --save request request-promise
```

## Spies vs Stubs

Spies and stubs are sometimes confused but they serve two different purposes. **Spies** live up to their name and the spy on functions but don't alter the function's behaviour at all. We can use them to tell us that something has been called and how. **Stubs** are similar to spies, and they support the sinon spy API, but they also alter the behaviour of the function. This means you can define how the function will respond. This allows you to better test the happy and unhappy paths in your code.

### Create the code

Create a new module in the `src` directory called `day5.js` and corresponding test file in the `test` directory called `day5.test.js`:

```javascript
import requestPromise from 'request-promise';

function day5(owner, repository) {
});

export default day5;
```

```javascript
import {expect} from 'chai';
import sinon from 'sinon';
import requestPromise from 'request-promise';
import day5 from '../src/day5';

describe('day5 tests', () => {
});
```

### Spy on the GitHub API

Let's spy on the GitHub API request and confirm that it's called once. Set up the spy to inspect `requestPromise.get` for the GET request we'll make to the API. You can then write a test that confirms that the spy was called only once.

```javascript
let spyRequestGet;

beforeEach(() => {
spyRequestGet = sinon.spy(requestPromise, 'get');
});

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

it('should call the expected endpoint once', () => {
return day5()
.then(() => {
expect(spyRequestGet.callCount).to.equal(1);
});
});
```

And write the code to pass this:

```javascript
function day5(owner, repository) {
const gitHubRepoGetUrl =
`https://api.github.com/repos/${owner}/${repository}`;

const requestOptions = {
uri: gitHubRepoGetUrl,
resolveWithFullResponse: true,
json: true,
headers: {
'User-Agent': 'JavaScript Testing Beginners Course'
}
};

// We need a few extra parameters
return requestPromise.get(requestOptions);
}
```

### Confirm the correct API endpoint

Let's write a test to confirm that the correct endpoint has been called.

```javascript
it('should call the expected endpoint url', () => {
const expectedGitHubUrl = 'https://api.github.com/repos/expressjs/express';
return day5('expressjs', 'express')
.then((data) => {
expect(spyRequestGet.getCall(0).args[0].uri)
.to.equal(expectedGitHubUrl);
});
});
```
9 changes: 8 additions & 1 deletion doc/notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@
5. Date describer - tell us if the date is in the future
6. Fake the current time

## Day 5 - GitHub stargazing with stubs & spies
## Day 5 - GitHub stargazing with spies & stubs

1. Use GitHub API to get information about stars for a repository
2. Use HTTP request with callback
3. Use sinon to spy on request - still hit API
4. Use sinon stub to stub HTTP request
5. Yield to a callback - happy path
6. Yield to a callback - error

## Day 6 - Simplify & automate your tests

Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@
"chai": "~4.1.1",
"mocha": "~3.5.0",
"sinon": "~3.2.1"
},
"dependencies": {
"request": "~2.81.0"
}
}
30 changes: 30 additions & 0 deletions src/day5.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import request from 'request';

function day5(owner, repository, callback) {
const gitHubUrl = `https://api.github.com/repos/${owner}/${repository}`;

const requestOptions = {
uri: gitHubUrl,
headers: {
'User-Agent': 'JavaScript Testing For Beginners'
},
resolveWithFullResponse: true,
json: true
};

request.get(requestOptions, (error, response, body) => {
if (response.statusCode == 403) {
callback({
success: false,
statusCode: response.statusCode,
error: 'API is rate limited - try again later'
})
} else {
callback({
stars: body.stargazers_count
});
}
});
}

export default day5;
119 changes: 119 additions & 0 deletions test/day5.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import {expect} from 'chai';
import sinon from 'sinon';
import request from 'request';
import day5 from '../src/day5';

describe('day5 tests', () => {

describe('using spies', () => {
let spyRequestGet;

beforeEach(() => {
spyRequestGet = sinon.spy(request, 'get');
});

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

// Should make 1 GET request
it('should make a get request once', (done) => {
day5('expressjs', 'express', (data) => {
expect(spyRequestGet.callCount)
.to.equal(1);

done();
})
});

// Should make request with expected URL
it('should make request with expected URL', (done) => {
day5('expressjs', 'express', (data) => {
expect(spyRequestGet.getCall(0).args[0].uri)
.to.equal('https://api.github.com/repos/expressjs/express');
done();
})
});
});

describe('using stubs', () => {
let stubRequestGet;

before(() => {
// before the test suite
});

after(() => {
// after the test suite
});

beforeEach(() => {
// before each test
stubRequestGet = sinon.stub(request, 'get');
});

afterEach(() => {
// after each test
stubRequestGet.restore();
});

// Should make 1 request
it('should make one GET request', (done) => {
stubRequestGet.yields(
null,
{statusCode: 200},
{stargazers_count: 1000}
);

day5('expressjs', 'express', (data) => {
expect(stubRequestGet.callCount)
.to.equal(1);

done();
});
});

// Should return correct data
it('should return expected data', (done) => {
const givenApiResponse = {
'stargazers_count': 100
};

stubRequestGet.yields(
null,
{statusCode: 200},
givenApiResponse
);

day5('expressjs', 'express', (data) => {
expect(data).to.deep.equal({
stars: givenApiResponse.stargazers_count
});
done();
})
});

// Should return correct data when error
it('should return expected data when rate limited', (done) => {
const givenApiResponse = {
'message': 'API rate limit exceeded',
'documentation_url': 'https://developer.github.com/v3/#rate-limiting'
};

stubRequestGet.yields(
new Error('API rate limit exceeded'),
{statusCode: 403},
givenApiResponse
);

day5('expressjs', 'express', (data) => {
expect(data).to.deep.equal({
success: false,
statusCode: 403,
error: 'API is rate limited - try again later'
});
done();
});
});
});
});

0 comments on commit 50fdc9f

Please sign in to comment.