Skip to content

Commit 50fdc9f

Browse files
authored
Merge pull request #6 from MarcL/day5-new
Day 5 : GitHub Stargazing with Spies & Stubs
2 parents 3ed1f4d + 3db1d72 commit 50fdc9f

File tree

5 files changed

+259
-1
lines changed

5 files changed

+259
-1
lines changed

doc/day5.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Day 5 notes - GitHub stargazing with spies and stubs
2+
3+
## What you'll do
4+
5+
Today you're going to look at calling the GitHub API using a HTTP request
6+
7+
## Install request-promise
8+
9+
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`:
10+
11+
```shell
12+
npm install --save request request-promise
13+
```
14+
15+
## Spies vs Stubs
16+
17+
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.
18+
19+
### Create the code
20+
21+
Create a new module in the `src` directory called `day5.js` and corresponding test file in the `test` directory called `day5.test.js`:
22+
23+
```javascript
24+
import requestPromise from 'request-promise';
25+
26+
function day5(owner, repository) {
27+
});
28+
29+
export default day5;
30+
```
31+
32+
```javascript
33+
import {expect} from 'chai';
34+
import sinon from 'sinon';
35+
import requestPromise from 'request-promise';
36+
import day5 from '../src/day5';
37+
38+
describe('day5 tests', () => {
39+
});
40+
```
41+
42+
### Spy on the GitHub API
43+
44+
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.
45+
46+
```javascript
47+
let spyRequestGet;
48+
49+
beforeEach(() => {
50+
spyRequestGet = sinon.spy(requestPromise, 'get');
51+
});
52+
53+
afterEach(() => {
54+
spyRequestGet.restore();
55+
});
56+
57+
it('should call the expected endpoint once', () => {
58+
return day5()
59+
.then(() => {
60+
expect(spyRequestGet.callCount).to.equal(1);
61+
});
62+
});
63+
```
64+
65+
And write the code to pass this:
66+
67+
```javascript
68+
function day5(owner, repository) {
69+
const gitHubRepoGetUrl =
70+
`https://api.github.com/repos/${owner}/${repository}`;
71+
72+
const requestOptions = {
73+
uri: gitHubRepoGetUrl,
74+
resolveWithFullResponse: true,
75+
json: true,
76+
headers: {
77+
'User-Agent': 'JavaScript Testing Beginners Course'
78+
}
79+
};
80+
81+
// We need a few extra parameters
82+
return requestPromise.get(requestOptions);
83+
}
84+
```
85+
86+
### Confirm the correct API endpoint
87+
88+
Let's write a test to confirm that the correct endpoint has been called.
89+
90+
```javascript
91+
it('should call the expected endpoint url', () => {
92+
const expectedGitHubUrl = 'https://api.github.com/repos/expressjs/express';
93+
return day5('expressjs', 'express')
94+
.then((data) => {
95+
expect(spyRequestGet.getCall(0).args[0].uri)
96+
.to.equal(expectedGitHubUrl);
97+
});
98+
});
99+
```

doc/notes.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@
3131
5. Date describer - tell us if the date is in the future
3232
6. Fake the current time
3333

34-
## Day 5 - GitHub stargazing with stubs & spies
34+
## Day 5 - GitHub stargazing with spies & stubs
35+
36+
1. Use GitHub API to get information about stars for a repository
37+
2. Use HTTP request with callback
38+
3. Use sinon to spy on request - still hit API
39+
4. Use sinon stub to stub HTTP request
40+
5. Yield to a callback - happy path
41+
6. Yield to a callback - error
3542

3643
## Day 6 - Simplify & automate your tests
3744

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@
2323
"chai": "~4.1.1",
2424
"mocha": "~3.5.0",
2525
"sinon": "~3.2.1"
26+
},
27+
"dependencies": {
28+
"request": "~2.81.0"
2629
}
2730
}

src/day5.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import request from 'request';
2+
3+
function day5(owner, repository, callback) {
4+
const gitHubUrl = `https://api.github.com/repos/${owner}/${repository}`;
5+
6+
const requestOptions = {
7+
uri: gitHubUrl,
8+
headers: {
9+
'User-Agent': 'JavaScript Testing For Beginners'
10+
},
11+
resolveWithFullResponse: true,
12+
json: true
13+
};
14+
15+
request.get(requestOptions, (error, response, body) => {
16+
if (response.statusCode == 403) {
17+
callback({
18+
success: false,
19+
statusCode: response.statusCode,
20+
error: 'API is rate limited - try again later'
21+
})
22+
} else {
23+
callback({
24+
stars: body.stargazers_count
25+
});
26+
}
27+
});
28+
}
29+
30+
export default day5;

test/day5.test.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import {expect} from 'chai';
2+
import sinon from 'sinon';
3+
import request from 'request';
4+
import day5 from '../src/day5';
5+
6+
describe('day5 tests', () => {
7+
8+
describe('using spies', () => {
9+
let spyRequestGet;
10+
11+
beforeEach(() => {
12+
spyRequestGet = sinon.spy(request, 'get');
13+
});
14+
15+
afterEach(() => {
16+
spyRequestGet.restore();
17+
});
18+
19+
// Should make 1 GET request
20+
it('should make a get request once', (done) => {
21+
day5('expressjs', 'express', (data) => {
22+
expect(spyRequestGet.callCount)
23+
.to.equal(1);
24+
25+
done();
26+
})
27+
});
28+
29+
// Should make request with expected URL
30+
it('should make request with expected URL', (done) => {
31+
day5('expressjs', 'express', (data) => {
32+
expect(spyRequestGet.getCall(0).args[0].uri)
33+
.to.equal('https://api.github.com/repos/expressjs/express');
34+
done();
35+
})
36+
});
37+
});
38+
39+
describe('using stubs', () => {
40+
let stubRequestGet;
41+
42+
before(() => {
43+
// before the test suite
44+
});
45+
46+
after(() => {
47+
// after the test suite
48+
});
49+
50+
beforeEach(() => {
51+
// before each test
52+
stubRequestGet = sinon.stub(request, 'get');
53+
});
54+
55+
afterEach(() => {
56+
// after each test
57+
stubRequestGet.restore();
58+
});
59+
60+
// Should make 1 request
61+
it('should make one GET request', (done) => {
62+
stubRequestGet.yields(
63+
null,
64+
{statusCode: 200},
65+
{stargazers_count: 1000}
66+
);
67+
68+
day5('expressjs', 'express', (data) => {
69+
expect(stubRequestGet.callCount)
70+
.to.equal(1);
71+
72+
done();
73+
});
74+
});
75+
76+
// Should return correct data
77+
it('should return expected data', (done) => {
78+
const givenApiResponse = {
79+
'stargazers_count': 100
80+
};
81+
82+
stubRequestGet.yields(
83+
null,
84+
{statusCode: 200},
85+
givenApiResponse
86+
);
87+
88+
day5('expressjs', 'express', (data) => {
89+
expect(data).to.deep.equal({
90+
stars: givenApiResponse.stargazers_count
91+
});
92+
done();
93+
})
94+
});
95+
96+
// Should return correct data when error
97+
it('should return expected data when rate limited', (done) => {
98+
const givenApiResponse = {
99+
'message': 'API rate limit exceeded',
100+
'documentation_url': 'https://developer.github.com/v3/#rate-limiting'
101+
};
102+
103+
stubRequestGet.yields(
104+
new Error('API rate limit exceeded'),
105+
{statusCode: 403},
106+
givenApiResponse
107+
);
108+
109+
day5('expressjs', 'express', (data) => {
110+
expect(data).to.deep.equal({
111+
success: false,
112+
statusCode: 403,
113+
error: 'API is rate limited - try again later'
114+
});
115+
done();
116+
});
117+
});
118+
});
119+
});

0 commit comments

Comments
 (0)