Testing web APIs
Upcoming SlideShare
Loading in...5
×
 

Testing web APIs

on

  • 1,844 views

Jakob Mattsson «Testing web APIs»​

Jakob Mattsson «Testing web APIs»​
Frontend Dev Conf'14
www.fdconf.by

Statistics

Views

Total Views
1,844
Views on SlideShare
312
Embed Views
1,532

Actions

Likes
0
Downloads
5
Comments
0

2 Embeds 1,532

http://dev.by 1531
http://digg.by 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Testing web APIs Testing web APIs Presentation Transcript

  • 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.
  • 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.
  • 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.
  • 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
  • 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”
  • 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
  • www.jakobm.com @jakobmattsson ! github.com/jakobmattsson/z-core Testing web APIs Simple and readable