Skip to content

Commit c9fea1d

Browse files
authored
Merge pull request #17 from marmelab/unit_tests
[RFR] Add unit tests for the server side
2 parents 945ace1 + 214d742 commit c9fea1d

File tree

15 files changed

+469
-229
lines changed

15 files changed

+469
-229
lines changed

src/jsonGraphqlExpress.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import graphqlHTTP from 'express-graphql';
2-
import { printSchema } from 'graphql';
3-
import { makeExecutableSchema } from 'graphql-tools';
4-
import getSchemaFromData from './introspection/getSchemaFromData';
5-
import resolver from './resolver';
2+
import schemaBuilder from './schemaBuilder';
63

74
/**
85
* An express middleware for a GraphQL endpoint serving data from the supplied json.
@@ -48,13 +45,8 @@ import resolver from './resolver';
4845
*
4946
* app.listen(PORT);
5047
*/
51-
export default data => {
52-
return graphqlHTTP({
53-
schema: makeExecutableSchema({
54-
typeDefs: printSchema(getSchemaFromData(data)),
55-
resolvers: resolver(data),
56-
logger: { log: e => console.log(e) }, // eslint-disable-line no-console
57-
}),
48+
export default data =>
49+
graphqlHTTP({
50+
schema: schemaBuilder(data),
5851
graphiql: true,
5952
});
60-
};

src/jsonGraphqlExpress.spec.js

Lines changed: 28 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -45,196 +45,34 @@ const gqlAgent = (query, variables) =>
4545
variables,
4646
});
4747

48-
describe('all* route', () => {
48+
describe('integration tests', () => {
4949
it('returns all entities by default', () =>
5050
gqlAgent('{ allPosts { id } }').expect({
5151
data: {
52-
allPosts: [{ id: 1 }, { id: 2 }, { id: 3 }],
52+
allPosts: [{ id: '1' }, { id: '2' }, { id: '3' }],
53+
},
54+
}));
55+
it('filters by string using the q filter in a case-insensitive way', () =>
56+
gqlAgent('{ allPosts(filter: { q: "lorem" }) { id } }').expect({
57+
data: {
58+
allPosts: [{ id: '1' }],
5359
},
5460
}));
55-
describe('pagination', () => {
56-
it('does not paginate when page is not set', () =>
57-
gqlAgent('{ allPosts(perPage: 1) { id } }').expect({
58-
data: {
59-
allPosts: [{ id: 1 }, { id: 2 }, { id: 3 }],
60-
},
61-
}));
62-
it('uses page to set page number', () =>
63-
Promise.all([
64-
gqlAgent('{ allPosts(page: 0) { id } }').expect({
65-
data: {
66-
allPosts: [{ id: 1 }, { id: 2 }, { id: 3 }],
67-
},
68-
}),
69-
gqlAgent('{ allPosts(page: 1) { id } }').expect({
70-
data: {
71-
allPosts: [],
72-
},
73-
}),
74-
]));
75-
it('uses perPage to set number of results per page', () =>
76-
Promise.all([
77-
gqlAgent('{ allPosts(page: 0, perPage: 1) { id } }').expect({
78-
data: {
79-
allPosts: [{ id: 1 }],
80-
},
81-
}),
82-
gqlAgent('{ allPosts(page: 1, perPage: 1) { id } }').expect({
83-
data: {
84-
allPosts: [{ id: 2 }],
85-
},
86-
}),
87-
gqlAgent('{ allPosts(page: 2, perPage: 1) { id } }').expect({
88-
data: {
89-
allPosts: [{ id: 3 }],
90-
},
91-
}),
92-
gqlAgent('{ allPosts(page: 3, perPage: 1) { id } }').expect({
93-
data: {
94-
allPosts: [],
95-
},
96-
}),
97-
gqlAgent('{ allPosts(page: 0, perPage: 2) { id } }').expect({
98-
data: {
99-
allPosts: [{ id: 1 }, { id: 2 }],
100-
},
101-
}),
102-
gqlAgent('{ allPosts(page: 1, perPage: 2) { id } }').expect({
103-
data: {
104-
allPosts: [{ id: 3 }],
105-
},
106-
}),
107-
gqlAgent('{ allPosts(page: 2, perPage: 2) { id } }').expect({
108-
data: {
109-
allPosts: [],
110-
},
111-
}),
112-
]));
113-
});
114-
describe('sort', () => {
115-
it('sorts data using sortField for the field', () =>
116-
Promise.all([
117-
gqlAgent('{ allPosts(sortField: "views") { id } }').expect({
118-
data: {
119-
allPosts: [{ id: 2 }, { id: 3 }, { id: 1 }],
120-
},
121-
}),
122-
gqlAgent('{ allPosts(sortField: "title") { id } }').expect({
123-
data: {
124-
allPosts: [{ id: 1 }, { id: 3 }, { id: 2 }],
125-
},
126-
}),
127-
]));
128-
it('sorts data using sortOrder for the sort direction', () =>
129-
Promise.all([
130-
gqlAgent(
131-
'{ allPosts(sortField: "views", sortOrder: "asc") { id } }',
132-
).expect({
133-
data: {
134-
allPosts: [{ id: 2 }, { id: 3 }, { id: 1 }],
135-
},
136-
}),
137-
gqlAgent(
138-
'{ allPosts(sortField: "views", sortOrder: "desc") { id } }',
139-
).expect({
140-
data: {
141-
allPosts: [{ id: 1 }, { id: 3 }, { id: 2 }],
142-
},
143-
}),
144-
]));
145-
});
146-
describe('filter', () => {
147-
it('filters by string on all text fields using the q filter', () =>
148-
gqlAgent('{ allPosts(filter: { q: "Lorem" }) { id } }').expect({
149-
data: {
150-
allPosts: [{ id: 1 }],
151-
},
152-
}));
153-
it('filters by string using the q filter in a case-insensitive way', () =>
154-
gqlAgent('{ allPosts(filter: { q: "lorem" }) { id } }').expect({
155-
data: {
156-
allPosts: [{ id: 1 }],
157-
},
158-
}));
159-
it('filters by value on each field using the related filter', () =>
160-
Promise.all([
161-
gqlAgent('{ allPosts(filter: { id: 2 }) { id } }').expect({
162-
data: {
163-
allPosts: [{ id: 2 }],
164-
},
165-
}),
166-
gqlAgent(
167-
'{ allPosts(filter: { title: "Sic Dolor amet" }) { id } }',
168-
).expect({
169-
data: {
170-
allPosts: [{ id: 3 }],
171-
},
172-
}),
173-
gqlAgent('{ allPosts(filter: { views: 65 }) { id } }').expect({
174-
data: {
175-
allPosts: [{ id: 2 }],
176-
},
177-
}),
178-
gqlAgent(
179-
'{ allPosts(filter: { user_id: 456 }) { id } }',
180-
).expect({
181-
data: {
182-
allPosts: [{ id: 2 }],
183-
},
184-
}),
185-
]));
186-
it('filters by value range on each integer field using the related filters', () =>
187-
Promise.all([
188-
gqlAgent(
189-
'{ allPosts(filter: { views_lt: 76 }) { id } }',
190-
).expect({
191-
data: {
192-
allPosts: [{ id: 2 }],
193-
},
194-
}),
195-
gqlAgent(
196-
'{ allPosts(filter: { views_lte: 76 }) { id } }',
197-
).expect({
198-
data: {
199-
allPosts: [{ id: 2 }, { id: 3 }],
200-
},
201-
}),
202-
gqlAgent(
203-
'{ allPosts(filter: { views_gt: 76 }) { id } }',
204-
).expect({
205-
data: {
206-
allPosts: [{ id: 1 }],
207-
},
208-
}),
209-
gqlAgent(
210-
'{ allPosts(filter: { views_gte: 76 }) { id } }',
211-
).expect({
212-
data: {
213-
allPosts: [{ id: 1 }, { id: 3 }],
214-
},
215-
}),
216-
]));
217-
});
218-
});
219-
220-
describe('Entity route', () => {
22161
it('gets an entity by id', () =>
222-
Promise.all([
223-
gqlAgent('{ Post(id: 1) { id } }').expect({
224-
data: {
225-
Post: { id: 1 },
226-
},
227-
}),
228-
gqlAgent('{ Post(id: 2) { id } }').expect({
229-
data: {
230-
Post: { id: 2 },
231-
},
232-
}),
233-
]));
62+
gqlAgent('{ Post(id: 1) { id } }').expect({
63+
data: {
64+
Post: { id: '1' },
65+
},
66+
}));
23467
it('gets all the entity fields', () =>
23568
gqlAgent('{ Post(id: 1) { id title views user_id } }').expect({
23669
data: {
237-
Post: { id: 1, title: 'Lorem Ipsum', views: 254, user_id: 123 },
70+
Post: {
71+
id: '1',
72+
title: 'Lorem Ipsum',
73+
views: 254,
74+
user_id: '123',
75+
},
23876
},
23977
}));
24078
it('throws an error when asked for a non existent field', () =>
@@ -246,30 +84,16 @@ describe('Entity route', () => {
24684
},
24785
],
24886
}));
249-
it('gets one to many relationship fields', () =>
250-
gqlAgent('{ Post(id: 1) { User { name } } }').expect({
87+
it('gets relationship fields', () =>
88+
gqlAgent('{ Post(id: 1) { User { name } Comments { body }} }').expect({
25189
data: {
252-
Post: { User: { name: 'John Doe' } },
90+
Post: {
91+
User: { name: 'John Doe' },
92+
Comments: [
93+
{ body: 'Consectetur adipiscing elit' },
94+
{ body: 'Nam molestie pellentesque dui' },
95+
],
96+
},
25397
},
25498
}));
255-
it('gets many to one relationship fields', () =>
256-
Promise.all([
257-
gqlAgent('{ Post(id: 1) { Comments { body } } }').expect({
258-
data: {
259-
Post: {
260-
Comments: [
261-
{ body: 'Consectetur adipiscing elit' },
262-
{ body: 'Nam molestie pellentesque dui' },
263-
],
264-
},
265-
},
266-
}),
267-
gqlAgent('{ Post(id: 2) { Comments { body } } }').expect({
268-
data: {
269-
Post: {
270-
Comments: [{ body: 'Sunt in culpa qui officia' }],
271-
},
272-
},
273-
}),
274-
]));
27599
});

src/resolver/Entity/index.spec.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import entity from './';
2+
3+
test('provides empty resolver for data without relationship', () =>
4+
expect(entity('posts', { posts: [] })).toEqual({}));
5+
6+
const data = {
7+
posts: [
8+
{ id: 1, title: 'Lorem Ipsum', user_id: 123 },
9+
{ id: 2, title: 'Ut enim ad minim', user_id: 456 },
10+
{ id: 3, title: 'Sic Dolor amet', user_id: 123 },
11+
],
12+
users: [{ id: 123, name: 'John Doe' }, { id: 456, name: 'Jane Doe' }],
13+
comments: [
14+
{ id: 987, post_id: 1, body: 'Consectetur adipiscing elit' },
15+
{ id: 995, post_id: 1, body: 'Nam molestie pellentesque dui' },
16+
{ id: 998, post_id: 2, body: 'Sunt in culpa qui officia' },
17+
],
18+
};
19+
20+
test('provides many to one relationship reolvers', () => {
21+
const { User } = entity('posts', data);
22+
expect(User(data.posts[0])).toEqual({ id: 123, name: 'John Doe' });
23+
expect(User(data.posts[1])).toEqual({ id: 456, name: 'Jane Doe' });
24+
const { Post } = entity('comments', data);
25+
expect(Post(data.comments[0])).toEqual({
26+
id: 1,
27+
title: 'Lorem Ipsum',
28+
user_id: 123,
29+
});
30+
expect(Post(data.comments[1])).toEqual({
31+
id: 1,
32+
title: 'Lorem Ipsum',
33+
user_id: 123,
34+
});
35+
expect(Post(data.comments[2])).toEqual({
36+
id: 2,
37+
title: 'Ut enim ad minim',
38+
user_id: 456,
39+
});
40+
});
41+
42+
test('provides one to many relationship reolvers', () => {
43+
const { Comments } = entity('posts', data);
44+
expect(Comments(data.posts[0])).toEqual([
45+
{ id: 987, post_id: 1, body: 'Consectetur adipiscing elit' },
46+
{ id: 995, post_id: 1, body: 'Nam molestie pellentesque dui' },
47+
]);
48+
expect(Comments(data.posts[1])).toEqual([
49+
{ id: 998, post_id: 2, body: 'Sunt in culpa qui officia' },
50+
]);
51+
expect(Comments(data.posts[2])).toEqual([]);
52+
const { Posts } = entity('users', data);
53+
expect(Posts(data.users[0])).toEqual([
54+
{ id: 1, title: 'Lorem Ipsum', user_id: 123 },
55+
{ id: 3, title: 'Sic Dolor amet', user_id: 123 },
56+
]);
57+
expect(Posts(data.users[1])).toEqual([
58+
{ id: 2, title: 'Ut enim ad minim', user_id: 456 },
59+
]);
60+
});

src/resolver/Mutation/create.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
export default entityData => (_, entity) => {
1+
export default (entityData = []) => (_, entity) => {
2+
const newId =
3+
entityData.length > 0 ? entityData[entityData.length - 1].id + 1 : 0;
24
const newEntity = {
3-
id: entityData[entityData.length - 1].id + 1,
5+
id: newId,
46
...entity,
57
};
68

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import create from './create';
2+
3+
test('returns a new object with id 0 on empty datastore', () => {
4+
expect(create()(null, {})).toEqual({ id: 0 });
5+
});
6+
7+
test('returns a new object with incremental id', () => {
8+
const data = [{ id: 1 }, { id: 3 }];
9+
expect(create(data)(null, {})).toEqual({ id: 4 });
10+
});
11+
12+
test('returns a new object using create data', () => {
13+
const data = [{ id: 0, value: 'foo' }];
14+
expect(create(data)(null, { value: 'toto' })).toEqual({
15+
id: 1,
16+
value: 'toto',
17+
});
18+
});
19+
20+
test('creates a new record', () => {
21+
const data = [{ id: 1 }, { id: 3 }];
22+
create(data)(null, { value: 'foo' });
23+
expect(data).toEqual([{ id: 1 }, { id: 3 }, { id: 4, value: 'foo' }]);
24+
});

src/resolver/Mutation/remove.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
export default entityData => (_, { id }) => {
1+
export default (entityData = []) => (_, { id }) => {
22
const parsedId = parseInt(id, 10); // FIXME fails for non-integer ids
33
const indexOfEntity = entityData.findIndex(e => e.id === parsedId);
44
const removedEntity = entityData[indexOfEntity];
55

6-
entityData = entityData.filter(e => e.id !== id);
6+
entityData.splice(indexOfEntity, 1);
77
return removedEntity;
88
};

0 commit comments

Comments
 (0)