What if we don't have api

Tomasz Ducin

28th October 2014, Warsaw

#2

(what if we don't have api)

A co, jeśli nie mamy API?

Tomasz Ducin

13th October 2014, Warsaw

#41

Tomek Ducin

JavaScript, Python, Java

senior software engineer @ Hewlett-Packard

blah, blah, blah...

Agenda

  • SPA architecture
  • API communication problems
  • mocking API
  • back to the future!

webpages

architecture

briefly

traditional

Single-Page Application

setting up a new project

focus on interface

how did my grandpa do it

  1. grab a cup of coffee
  2. start reading specification
  3. quit reading specification after 10 minutes because it's boring
  4. start implementing interface foundations
  5. start implementing the API

implementing the API

  1. choose a framework
  2. initialize project
  3. handle multiple file formats (future proofing)
  4. set up a database
  5. populate a database with fake data
  6. no, no... a generator would be better
  7. an object-oriented and well-designed fake data generator

problem #1

building a working interface requires a working API first

Work Synchronization

problem #2

developing new feature forces to synchronise FE & BE work

Testing

interface testing

depends on the API

problem #3

configuring automatic interface tests requires deploying API as well

an alternative

mock your API

mocking browser features

// create and setup filters
this.fs = sinon.fakeServer.create();

this.fs.xhr.useFilters = true;
this.fs.xhr.addFilter(
  function(method, url, async, username, password) {
    return !(new RegExp(this.urlRoot)).test(url);
});
// configure model list resource
this.fs.respondWith("GET", this.urlRoot + '/todo',
  [200, { "Content-Type": "application/json" },
    JSON.stringify(inMemoryData) ]);
// configure single model item resource
this.fs.respondWith("POST", this.urlRoot + '/todo',
  function (xhr) {
    var todo,
      obj = JSON.parse(xhr.requestBody);
    if (obj.order === null) { // new object
      delete obj.order;
      inMemoryData.push(obj);
    } else if (obj.oldTitle) { // "title" attribute changed
      todo = _.find(inMemoryData, function(item){
        return item.title == obj.oldTitle; });
      todo.title = obj.title;
    } else { // "completed" attribute changed
      todo = _.find(inMemoryData, function(item){
        return item.title == obj.title; });
      todo.completed = obj.completed;
    }
    console.log(inMemoryData);
    xhr.respond(200, { "Content-Type": "application/json" });
  });

mocking framework features

// define standard BB models and collections
var Book = Backbone.Model.extend({
    defaults: {
        title: "Unknown title",
        author: "Unknown author"
    }
});
var Books = Backbone.Collection.extend({
    model: Book,
    url: "library-app/books"
});
fauxServer.addRoute("createBook", "library-app/books", "POST",
  function (context) {
    // Every handler receives a 'context' parameter.
    // Use context.data (a hash of Book attributes)
    // to create the Book entry in your persistence layer:
    context.data.id = newId(); // assign an id to the new book
    books.push(context.data);  // save to persistence layer
    return context.data;
});
fauxServer.addRoutes({
    createBook: {
        urlExp: "library-app/books",
        httpMethod: "POST",
        handler: function (context) {
            // Create book using context.data
            // Save to persistence layer
            // Return attributes of newly created book
        }
    },
    readBook: {
        urlExp: "library-app/books/:id",
        httpMethod: "GET",
        handler: function (context, bookId) {
          // Return attributes of book with id 'bookId'
        }
    },
    // ...
    //...
    updateBook: {
        urlExp: "library-app/books/:id",
        httpMethod: "PUT",
        handler: function (context, bookId) {
          // Update stored book with id 'bookId'
          // using attributes in context.data
          // Return updated attributes
        }
    },
    deleteBook: {
        urlExp: "library-app/books/:id",
        httpMethod: "DELETE",
        handler: function (context, bookId) {
          // Delete stored book of id 'bookId'
        }
    }
}

Summary

Mocking the API tries to solve three problems:

  1. don't work on API as long as you don't have to
  2. FE is independent on BE, so both can be developed separately
  3. removes the tests dependency on deploying a separate API instance

mock n'roll

  • response timeout / delay
  • JavaScript functions used, including:
  • reacting to request parameters
  • randomizing responses
  • and many more, depending on the tool used

Q & A

attribution

images used in this presentation are scenes from the magnificent 'Back to the Future' trilogy (1985, 1989, 1990) by Robert Zemeckis