SlideShare a Scribd company logo
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.
Testing web APIs
Testing web APIs
Social
coding
FishBrain is the fastest, easiest way
to log and share your fishing.

!
Get instant updates from anglers on
nearby lakes, rivers, and the coast.
Testing web APIs
Simple and readable
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
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
});
});
});
});
});
});
});
});
});
});
*No, this is not a tutorial
Promises 101*
getJSON({
url: '/somewhere/over/the/rainbow',
success: function(result) {
// deal with it
}
});
getJSON({
url: '/somewhere/over/the/rainbow',
success: function(result) {
// deal with it
}
});
rainbows.then(function(result) {
// deal with it
});
var rainbows = getJSON({
url: '/somewhere/over/the/rainbow'
});
getJSON({
url: '/somewhere/over/the/rainbow',
success: function(result) {
// deal with it
}
});
rainbows.then(function(result) {
// deal with it
});
var rainbows = getJSON({
url: '/somewhere/over/the/rainbow'
});
f(rainbows);
Why is that
a good idea?
• Loosen up the coupling
• Superior error handling
• Simplified async
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)
That’s enough 101
(even though people barely talk about
the last - and most important - idea)
Promises are not new
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
They’ve even
made it into ES6
They’ve even
made it into ES6
Already implemented
natively in
Firefox 30Chrome 33
So why are you not
using them?
So why are you not
using them?
How to draw an owl
1. Draw some
circles
How to draw an owl
1. Draw some
circles
2.Draw the rest
of the owl
How to draw an owl
We want to
draw owls.
!
Not circles.
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
That’s circles.
!
Nice circles!
!
Still circles.
browser.init({browserName:'chrome'}, function() {
browser.get("http://admc.io/wd/test-pages/guinea-pig.html", function() {
browser.title(function(err, title) {
title.should.include('WD');
browser.elementById('i am a link', function(err, el) {
browser.clickElement(el, function() {
browser.eval("window.location.href", function(err, href) {
href.should.include('guinea-pig2');
browser.quit();
});
});
});
});
});
});
Node.js WebDriver
Before promises
browser
.init({ browserName: 'chrome' })
.then(function() {
return browser.get("http://admc.io/wd/test-pages/guinea-pig.html");
})
.then(function() { return browser.title(); })
.then(function(title) {
title.should.include('WD');
return browser.elementById('i am a link');
})
.then(function(el) { return browser.clickElement(el); })
.then(function() {
return browser.eval("window.location.href");
})
.then(function(href) { href.should.include('guinea-pig2'); })
.fin(function() { return browser.quit(); })
.done();
After promises
Used to be
callback-hell.
!
Now it is
then-hell.
Testing web APIs
Testing web APIs
These examples are
just a different way
of doing async.
!
It’s still uncomfortable.
It’s still circles!
The point of promises:
!
Make async code
as straightforward
as sync code
The point of promises:
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
Let me elaborate
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
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.
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.
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
});
});
});
});
});
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.
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.
Testing web APIs
1 Promises out: Always return promises -
not callback
2 Promises in: Functions should accept
promises as well as regular values
Three requirements
Awesome!
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
Augmenting objects
is a sensitive topic
Extending prototypes
!
vs
!
wrapping objects
You’re writing Course of action
An app Do whatever you want
A library
Do not modify the

damn prototypes
Complimentary
decision matrix
Testing web APIs
Promises are already
wrapped objects
_(names)
.chain()
.unique()
.shuffle()
.first(3)
.value()
Chaining usually requires a
method to ”unwrap”
or repeated wrapping
u = _(names).unique()
s = _(u).shuffle()
f = _(s).first(3)
Promises already have a
well-defined way of
unwrapping.
_(names)
.unique()
.shuffle()
.first(3)
.then(function(values) {
// do stuff with values...
})
If underscore/lodash
wrapped promises
But they don’t
Enter
!
Z
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?
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
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
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
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”
Testing web APIs
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
Augmentation for promises
Z.mixin(zUnderscore);
Z.mixin(zBuiltins);
!
var comment = get('/comments/42');
!
comment.get('text').toLowerCase().first(5);
3
There are prepared
packages to mixin
entire libraries
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
Make async code
as straightforward
as sync code
Enough with
the madness
You don’t need a lib
to do these things
But please
Testing web APIs
www.jakobm.com @jakobmattsson
!
github.com/jakobmattsson/z-core
Testing web APIs
Simple and readable

More Related Content

KEY
Rails Presentation (Anton Dmitriyev)
PPTX
Behat - Drupal South 2018
PDF
Finding Restfulness - Madrid.rb April 2014
PDF
WordPress for the modern PHP developer
PDF
Crafting Quality PHP Applications (ConFoo YVR 2017)
PDF
Cucumber Ru09 Web
PDF
Kicking off with Zend Expressive and Doctrine ORM (ConFoo YVR 2017)
PDF
How to make Ajax work for you
Rails Presentation (Anton Dmitriyev)
Behat - Drupal South 2018
Finding Restfulness - Madrid.rb April 2014
WordPress for the modern PHP developer
Crafting Quality PHP Applications (ConFoo YVR 2017)
Cucumber Ru09 Web
Kicking off with Zend Expressive and Doctrine ORM (ConFoo YVR 2017)
How to make Ajax work for you

What's hot (20)

PDF
jQuery Proven Performance Tips & Tricks
PDF
Deploying a Location-Aware Ember Application
PDF
Crafting Quality PHP Applications (PHPkonf 2018)
PPTX
Maintainable JavaScript 2012
PPTX
End-to-end testing with geb
PDF
Crafting Quality PHP Applications (PHP Benelux 2018)
PDF
Crafting Quality PHP Applications (Bucharest Tech Week 2017)
PDF
Crafting Quality PHP Applications (PHP Joburg Oct 2019)
ODP
Step objects
PPT
Eugene Andruszczenko: jQuery
PDF
Outside-in Development with Cucumber and Rspec
PDF
Dip Your Toes in the Sea of Security (ConFoo YVR 2017)
PDF
Django Heresies
PDF
Best practices for crafting high quality PHP apps (php[world] 2019)
PDF
Rails for Beginners - Le Wagon
PDF
Turn your spaghetti code into ravioli with JavaScript modules
PDF
Building a Single Page Application using Ember.js ... for fun and profit
PDF
Selenium bootcamp slides
PDF
Cucumber
PDF
Enabling agile devliery through enabling BDD in PHP projects
jQuery Proven Performance Tips & Tricks
Deploying a Location-Aware Ember Application
Crafting Quality PHP Applications (PHPkonf 2018)
Maintainable JavaScript 2012
End-to-end testing with geb
Crafting Quality PHP Applications (PHP Benelux 2018)
Crafting Quality PHP Applications (Bucharest Tech Week 2017)
Crafting Quality PHP Applications (PHP Joburg Oct 2019)
Step objects
Eugene Andruszczenko: jQuery
Outside-in Development with Cucumber and Rspec
Dip Your Toes in the Sea of Security (ConFoo YVR 2017)
Django Heresies
Best practices for crafting high quality PHP apps (php[world] 2019)
Rails for Beginners - Le Wagon
Turn your spaghetti code into ravioli with JavaScript modules
Building a Single Page Application using Ember.js ... for fun and profit
Selenium bootcamp slides
Cucumber
Enabling agile devliery through enabling BDD in PHP projects
Ad

Viewers also liked (9)

PDF
How to survive on Magento platform
PPTX
Александр Анцыпов. REST: вывод традиционных систем на новый уровень
PDF
Андрей Светлов. Aiohttp
PPS
Magento performance
PDF
Александр Белокрылов. Java 8: Create The Future
PPTX
Writing extensions for Xcommerce
PDF
В погоне за производительностью
PPTX
Если у вас нету тестов...
PPTX
Migrate your React.js application from (m)Observable to Redux
How to survive on Magento platform
Александр Анцыпов. REST: вывод традиционных систем на новый уровень
Андрей Светлов. Aiohttp
Magento performance
Александр Белокрылов. Java 8: Create The Future
Writing extensions for Xcommerce
В погоне за производительностью
Если у вас нету тестов...
Migrate your React.js application from (m)Observable to Redux
Ad

Similar to Testing web APIs (20)

PDF
Getting Answers to Your Testing Questions
PPTX
Lect-5--JavaScript-Intro-12032024-105816am.pptx
PDF
How to actually use promises - Jakob Mattsson, FishBrain
PDF
Controller Testing: You're Doing It Wrong
PDF
JavaScript Interview Questions Part - 1.pdf
PPTX
Angular js
PPTX
Java script
PDF
Build a game with javascript (may 21 atlanta)
PDF
Plugin Development @ WordCamp Norway 2014
PDF
Intro to javascript (6:19)
PDF
JSON REST API for WordPress
PDF
Building Better Web APIs with Rails
PDF
Don't RTFM, WTFM - Open Source Documentation - German Perl Workshop 2010
KEY
Introdution to Node.js
PDF
Thinkful - Intro to JavaScript
PPTX
Why you should be using the shiny new C# 6.0 features now!
PDF
Intro to JavaScript - Thinkful LA, June 2017
PDF
Testing swagger contracts without contract based testing
PPTX
Journey To The Front End World - Part3 - The Machine
PDF
SproutCore and the Future of Web Apps
Getting Answers to Your Testing Questions
Lect-5--JavaScript-Intro-12032024-105816am.pptx
How to actually use promises - Jakob Mattsson, FishBrain
Controller Testing: You're Doing It Wrong
JavaScript Interview Questions Part - 1.pdf
Angular js
Java script
Build a game with javascript (may 21 atlanta)
Plugin Development @ WordCamp Norway 2014
Intro to javascript (6:19)
JSON REST API for WordPress
Building Better Web APIs with Rails
Don't RTFM, WTFM - Open Source Documentation - German Perl Workshop 2010
Introdution to Node.js
Thinkful - Intro to JavaScript
Why you should be using the shiny new C# 6.0 features now!
Intro to JavaScript - Thinkful LA, June 2017
Testing swagger contracts without contract based testing
Journey To The Front End World - Part3 - The Machine
SproutCore and the Future of Web Apps

More from FDConf (20)

PPT
Антон Киршанов - «Квант изменения. Реактивные реакции на React.
PDF
Игорь Еростенко - Создаем виртуальный тур
PDF
Илья Климов - Reason: маргиналы против хайпа
PDF
Максим Щепелин - Доставляя веб-контент в игру
PDF
Александр Черноокий - Как правило "победитель получает все" работает и не раб...
PDF
Михаил Волчек - Что такое Цифровая мастерская?
PDF
Radoslav Stankov - Handling GraphQL with React and Apollo
PDF
Виктор Русакович - Выборы, выборы, все фреймворки… приторны
PDF
Slobodan Stojanovic - 8 1/2 things about serverless
PPTX
Тимофей Лавренюк - Почему мне зашел PWA?
PPTX
Dart: питание и сила для вашего проекта
PDF
Scalable Angular 2 Application Architecture
PPTX
JavaScript: прошлое, настоящее и будущее.
PDF
CSSO — сжимаем CSS
PDF
Redux. From twitter hype to production
PDF
Будь первым
PDF
"Service Worker: Let Your Web App Feel Like a Native "
PDF
"Пиринговый веб на JavaScript"
PDF
«I knew there had to be a better way to build mobile app»​
PDF
«Как перестать отлаживать асинхронные вызовы и начать жить»​
Антон Киршанов - «Квант изменения. Реактивные реакции на React.
Игорь Еростенко - Создаем виртуальный тур
Илья Климов - Reason: маргиналы против хайпа
Максим Щепелин - Доставляя веб-контент в игру
Александр Черноокий - Как правило "победитель получает все" работает и не раб...
Михаил Волчек - Что такое Цифровая мастерская?
Radoslav Stankov - Handling GraphQL with React and Apollo
Виктор Русакович - Выборы, выборы, все фреймворки… приторны
Slobodan Stojanovic - 8 1/2 things about serverless
Тимофей Лавренюк - Почему мне зашел PWA?
Dart: питание и сила для вашего проекта
Scalable Angular 2 Application Architecture
JavaScript: прошлое, настоящее и будущее.
CSSO — сжимаем CSS
Redux. From twitter hype to production
Будь первым
"Service Worker: Let Your Web App Feel Like a Native "
"Пиринговый веб на JavaScript"
«I knew there had to be a better way to build mobile app»​
«Как перестать отлаживать асинхронные вызовы и начать жить»​

Recently uploaded (20)

PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PPTX
Group 1 Presentation -Planning and Decision Making .pptx
PDF
Network Security Unit 5.pdf for BCA BBA.
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PDF
Spectral efficient network and resource selection model in 5G networks
PDF
gpt5_lecture_notes_comprehensive_20250812015547.pdf
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PPTX
Tartificialntelligence_presentation.pptx
PDF
Approach and Philosophy of On baking technology
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
Accuracy of neural networks in brain wave diagnosis of schizophrenia
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PDF
cuic standard and advanced reporting.pdf
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
PDF
Assigned Numbers - 2025 - Bluetooth® Document
Reach Out and Touch Someone: Haptics and Empathic Computing
Group 1 Presentation -Planning and Decision Making .pptx
Network Security Unit 5.pdf for BCA BBA.
Digital-Transformation-Roadmap-for-Companies.pptx
Spectral efficient network and resource selection model in 5G networks
gpt5_lecture_notes_comprehensive_20250812015547.pdf
Per capita expenditure prediction using model stacking based on satellite ima...
Building Integrated photovoltaic BIPV_UPV.pdf
Tartificialntelligence_presentation.pptx
Approach and Philosophy of On baking technology
20250228 LYD VKU AI Blended-Learning.pptx
Accuracy of neural networks in brain wave diagnosis of schizophrenia
Dropbox Q2 2025 Financial Results & Investor Presentation
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
cuic standard and advanced reporting.pdf
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Diabetes mellitus diagnosis method based random forest with bat algorithm
Encapsulation_ Review paper, used for researhc scholars
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
Assigned Numbers - 2025 - Bluetooth® Document

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.
  • 5. FishBrain is the fastest, easiest way to log and share your fishing. ! Get instant updates from anglers on nearby lakes, rivers, and the coast.
  • 6. Testing web APIs Simple and readable
  • 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
  • 8. 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 }); }); }); }); }); }); }); }); }); });
  • 9. *No, this is not a tutorial Promises 101*
  • 11. getJSON({ url: '/somewhere/over/the/rainbow', success: function(result) { // deal with it } }); rainbows.then(function(result) { // deal with it }); var rainbows = getJSON({ url: '/somewhere/over/the/rainbow' });
  • 12. getJSON({ url: '/somewhere/over/the/rainbow', success: function(result) { // deal with it } }); rainbows.then(function(result) { // deal with it }); var rainbows = getJSON({ url: '/somewhere/over/the/rainbow' }); f(rainbows);
  • 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
  • 19. They’ve even made it into ES6 Already implemented natively in Firefox 30Chrome 33
  • 20. So why are you not using them?
  • 21. So why are you not using them?
  • 22. How to draw an owl
  • 23. 1. Draw some circles How to draw an owl
  • 24. 1. Draw some circles 2.Draw the rest of the owl How to draw an owl
  • 25. We want to draw owls. ! Not circles.
  • 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
  • 28. browser.init({browserName:'chrome'}, function() { browser.get("http://admc.io/wd/test-pages/guinea-pig.html", function() { browser.title(function(err, title) { title.should.include('WD'); browser.elementById('i am a link', function(err, el) { browser.clickElement(el, function() { browser.eval("window.location.href", function(err, href) { href.should.include('guinea-pig2'); browser.quit(); }); }); }); }); }); }); Node.js WebDriver Before promises
  • 29. browser .init({ browserName: 'chrome' }) .then(function() { return browser.get("http://admc.io/wd/test-pages/guinea-pig.html"); }) .then(function() { return browser.title(); }) .then(function(title) { title.should.include('WD'); return browser.elementById('i am a link'); }) .then(function(el) { return browser.clickElement(el); }) .then(function() { return browser.eval("window.location.href"); }) .then(function(href) { href.should.include('guinea-pig2'); }) .fin(function() { return browser.quit(); }) .done(); After promises
  • 33. These examples are just a different way of doing async. ! It’s still uncomfortable. It’s still circles!
  • 34. The point of promises:
  • 35. ! Make async code as straightforward as sync code The point of promises:
  • 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.
  • 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
  • 48. Augmenting objects is a sensitive topic
  • 50. You’re writing Course of action An app Do whatever you want A library Do not modify the
 damn prototypes Complimentary decision matrix
  • 53. _(names) .chain() .unique() .shuffle() .first(3) .value() Chaining usually requires a method to ”unwrap” or repeated wrapping u = _(names).unique() s = _(u).shuffle() f = _(s).first(3)
  • 54. Promises already have a well-defined way of unwrapping.
  • 55. _(names) .unique() .shuffle() .first(3) .then(function(values) { // do stuff with values... }) If underscore/lodash wrapped promises
  • 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”
  • 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
  • 65. Augmentation for promises Z.mixin(zUnderscore); Z.mixin(zBuiltins); ! var comment = get('/comments/42'); ! comment.get('text').toLowerCase().first(5); 3 There are prepared packages to mixin entire libraries
  • 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
  • 67. Make async code as straightforward as sync code Enough with the madness
  • 68. You don’t need a lib to do these things