The Codex of Business Writing Software for Real-World Solutions 2.pptx
Testing web APIs
1. jakobm.com
@jakobmattsson
I’m a coder first and foremost. I also help
companies recruit coders, train coders and
architect software. Sometimes I do technical
due diligence and speak at conferences.
!
Want more? Read my story or blog.
7. Example - Testing a blog API
• Create a blog
• Create two blog entries
• Create two users
• Create a lotal of three comments from
those users, on the two posts
• Request the stats for the blog and
check if the given number of entries
and comments are correct
13. Why is that
a good idea?
• Loosen up the coupling
• Superior error handling
• Simplified async
14. Why is that
a good idea?
• Loosen up the coupling
• Superior error handling
• Simplified async
But in particular, it abstracts away the temporal
dependencies in your program (or in this case, test)
15. That’s enough 101
(even though people barely talk about
the last - and most important - idea)
17. Promises are not new
http://github.com/kriskowal/q
http://www.html5rocks.com/en/tutorials/es6/promises
http://domenic.me/2012/10/14/youre-missing-the-point-of-promises
http://www.promisejs.org
https://github.com/bellbind/using-promise-q
https://github.com/tildeio/rsvp.js
https://github.com/cujojs/when
26. getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
!
// Map our array of chapter urls to
// an array of chapter json promises.
// This makes sture they all download parallel.
return story.chapterUrls.map(getJSON)
.reduce(function(sequence, chapterPromise) {
// Use reduce to chain the promises together,
// adding content to the page for each chapter
return sequence.then(function() {
// Wait for everything in the sequence so far,
// then wait for this chapter to arrive.
return chapterPromise;
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
}).then(function() {
addTextToPage('All done');
}).catch(function(err) {
// catch any error that happened along the way
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
});
As announced
for ES6
36. 1 Promises out: Always return promises -
not callback
2 Promises in: Functions should accept
promises as well as regular values
3 Promises between: Augment promises as
you augment regular objects
Three requirements
38. Example - Testing a blog API
• Create a blog
• Create two blog entries
• Create some users
• Create some comments from those
users, on the two posts
• Request the stats for the blog and
check if the given number of entries
and comments are correct
39. post('/blogs', {
name: 'My blog'
}, function(err, blog) {
! post(concatUrl('blogs', blog.id, 'entries'), {
title: 'my first post’,
body: 'Here is the text of my first post'
}, function(err, entry1) {
! post(concatUrl('blogs', blog.id, 'entries'), {
title: 'my second post’,
body: 'I do not know what to write any more...'
}, function(err, entry2) {
! post('/user', {
name: 'john doe’
}, function(err, visitor1) {
! post('/user', {
name: 'jane doe'
}, function(err, visitor2) {
! post('/comments', {
userId: visitor1.id,
entryId: entry1.id,
text: "well written dude"
}, function(err, comment1) {
! post('/comments', {
userId: visitor2.id,
entryId: entry1.id,
text: "like it!"
}, function(err, comment2) {
! post('/comments', {
userId: visitor2.id,
entryId: entry2.id,
text: "nah, crap"
}, function(err, comment3) {
! get(concatUrl('blogs', blog.id), function(err, blogInfo) {
assertEquals(blogInfo, {
name: 'My blog',
numberOfEntries: 2,
numberOfComments: 3
});
});
});
});
});
});
});
});
});
});
https://
github.com/
jakobmattsson/
z-presentation/
blob/master/
promises-in-out/
1-naive.js
1
Note: without narration, this slide lacks a lot of
context. Open the file above and read the
commented version for the full story.
40. post('/blogs', {
name: 'My blog'
}, function(err, blog) {
! var entryData = [{
title: 'my first post',
body: 'Here is the text of my first post'
}, {
title: 'my second post',
body: 'I do not know what to write any more...'
}]
! async.forEach(entryData, function(entry, callback), {
post(concatUrl('blogs', blog.id, 'entries'), entry, callback);
}, function(err, entries) {
! var usernames = ['john doe', 'jane doe'];
! async.forEach(usernames, function(user, callback) {
post('/user', { name: user }, callback);
}, function(err, visitors) {
! var commentsData = [{
userId: visitor[0].id,
entryId: entries[0].id,
text: "well written dude"
}, {
userId: visitor[1].id,
entryId: entries[0].id,
text: "like it!"
}, {
userId: visitor[1].id,
entryId: entries[1].id,
text: "nah, crap"
}];
! async.forEach(commentsData, function(comment, callback) {
post('/comments', comment, callback);
}, function(err, comments) {
! get(concatUrl('blogs', blog.id), function(err, blogInfo) {
! assertEquals(blogInfo, {
name: 'My blog',
numberOfEntries: 2,
numberOfComments: 3
});
});
});
});
});
});
https://
github.com/
jakobmattsson/
z-presentation/
blob/master/
promises-in-out/
2-async.js
2
Note: without narration, this slide lacks a lot of
context. Open the file above and read the
commented version for the full story.
41. https://
github.com/
jakobmattsson/
z-presentation/
blob/master/
promises-in-out/
3-async-more-parallel.js
3
Note: without narration, this slide lacks a lot of
context. Open the file above and read the
commented version for the full story.
post('/blogs', {
name: 'My blog'
}, function(err, blog) {
! async.parallel([
function(callback) {
var entryData = [{
title: 'my first post',
body: 'Here is the text of my first post'
}, {
title: 'my second post',
body: 'I do not know what to write any more...'
}];
async.forEach(entryData, function(entry, callback), {
post(concatUrl('blogs', blog.id, 'entries'), entry, callback);
}, callback);
},
function(callback) {
var usernames = ['john doe', 'jane doe’];
async.forEach(usernames, function(user, callback) {
post('/user', { name: user }, callback);
}, callback);
}
], function(err, results) {
! var entries = results[0];
var visitors = results[1];
! var commentsData = [{
userId: visitors[0].id,
entryId: entries[0].id,
text: "well written dude"
}, {
userId: visitors[1].id,
entryId: entries[0].id,
text: "like it!"
}, {
userId: visitors[1].id,
entryId: entries[1].id,
text: "nah, crap"
}];
! async.forEach(commentsData, function(comment, callback) {
post('/comments', comment, callback);
}, function(err, comments) {
! get(concatUrl('blogs', blog.id), function(err, blogInfo) {
assertEquals(blogInfo, {
name: 'My blog',
numberOfEntries: 2,
numberOfComments: 3
});
});
});
});
});
42. post('/blogs', {
name: 'My blog'
}).then(function(blog) {
! var visitor1 = post('/user', {
name: 'john doe'
});
! var visitor2 = post('/user', {
name: 'jane doe'
});
! var entry1 = post(concatUrl('blogs', blog.id, 'entries'), {
title: 'my first post',
body: 'Here is the text of my first post'
});
! var entry2 = post(concatUrl('blogs', blog.id, 'entries'), {
title: 'my second post',
body: 'I do not know what to write any more...'
});
! var comment1 = all(entry1, visitor1).then(function(e1, v1) {
post('/comments', {
userId: v1.id,
entryId: e1.id,
text: "well written dude"
});
});
! var comment2 = all(entry1, visitor2).then(function(e1, v2) {
post('/comments', {
userId: v2.id,
entryId: e1.id,
text: "like it!"
});
});
! var comment3 = all(entry2, visitor2).then(function(e2, v2) {
post('/comments', {
userId: v2.id,
entryId: e2.id,
text: "nah, crap"
});
});
! all(comment1, comment2, comment3).then(function() {
get(concatUrl('blogs', blog.id)).then(function(blogInfo) {
assertEquals(blogInfo, {
name: 'My blog',
numberOfEntries: 2,
numberOfComments: 3
});
});
});
});
https://
github.com/
jakobmattsson/
z-presentation/
blob/master/
promises-in-out/
4-promises-convoluted.js
4
Note: without narration, this slide lacks a lot of
context. Open the file above and read the
commented version for the full story.
43. var blog = post('/blogs', {
name: 'My blog'
});
!var entry1 = post(concatUrl('blogs', blog.get('id'), 'entries'), {
title: 'my first post',
body: 'Here is the text of my first post'
});
!var entry2 = post(concatUrl('blogs', blog.get('id'), 'entries'), {
title: 'my second post',
body: 'I do not know what to write any more...'
});
!var visitor1 = post('/user', {
name: 'john doe'
});
!var visitor2 = post('/user', {
name: 'jane doe'
});
!var comment1 = post('/comments', {
userId: visitor1.get('id'),
entryId: entry1.get('id'),
text: "well written dude"
});
!var comment2 = post('/comments', {
userId: visitor2.get('id'),
entryId: entry1.get('id'),
text: "like it!"
});
!var comment3 = post('/comments', {
userId: visitor2.get('id'),
entryId: entry2.get('id'),
text: "nah, crap"
});
!var allComments = [comment1, comment2, comment2];
!var blogInfoUrl = concatUrl('blogs', blog.get('id'));
!var blogInfo = getAfter(blogInfoUrl, allComments);
!assertEquals(blogInfo, {
name: 'My blog',
numberOfEntries: 2,
numberOfComments: 3
});
https://
github.com/
jakobmattsson/
z-presentation/
blob/master/
promises-in-out/
5-promises-nice.js
5
Note: without narration, this slide lacks a lot of
context. Open the file above and read the
commented version for the full story.
44.
45. 1 Promises out: Always return promises -
not callback
2 Promises in: Functions should accept
promises as well as regular values
Three requirements
47. 1 Promises out: Always return promises -
not callback
2 Promises in: Functions should accept
promises as well as regular values
3 Promises between: Augment promises as
you augment regular objects
Three requirements
58. 1 Deep resolution: Resolve any kind of
object/array/promise/values/whatever
2 Make functions promise-friendly: Sync or
async doesn’t matter; will accept promises
3 Augmentation for promises: jQuery/
underscore/lodash-like extensions
What is Z?
59. Deep resolution
var data = {
userId: visitor1.get('id'),
entryId: entry1.get('id'),
text: 'well written dude'
};
!
Z(data).then(function(result) {
!
// result is now: {
// userId: 123,
// entryId: 456,
// text: 'well written dude'
// }
!
});
Takes any object
and resolves all
promises in it.
!
Like Q and Q.all,
but deep
1
60. Promise-friendly functions
var post = function(url, data, callback) {
// POSTs `data` to `url` and
// then invokes `callback`
};
!
post = Z.bindAsync(post);
!
var comment1 = post('/comments', {
userId: visitor1.get('id'),
entryId: entry1.get('id'),
text: 'well written dude'
});
bindAsync creates
a function that
takes promises as
arguments and
returns a promise.
2
61. Promise-friendly functions
var add = function(x, y) {
return x + y;
};
!
add = Z.bindSync(add);
!
var sum = add(v1.get('id'), e1.get('id'));
!
var comment1 = post('/comments', {
userId: visitor1.get('id'),
entryId: entry1.get('id'),
text: 'well written dude',
likes: sum
});
2
bindSync does that
same, for functions
that are not async
62. Augmentation for promises
var commentData = get('/comments/42');
!
var text = commentData.get('text');
!
var lowerCased = text.then(function(text) {
return text.toLowerCase();
});
3
Without
augmentation
every operation
has to be wrapped
in ”then”
63.
64. Augmentation for promises
Z.mixin({
toLowerCase: function() {
return this.value.toLowerCase();
}
});
!
var commentData = get('/comments/42');
!
commentData.get('text').toLowerCase();
3
Z has mixin to
solve this
!
Note that Z is not
explicitly applied
to the promise
66. 1 Promises out: Always return promises -
not callback
2 Promises in: Functions should accept
promises as well as regular values
3 Promises between: Augment promises as
you augment regular objects
Three requirements