How to create a HTML5 Ad Banner

Screen Shot 2015-09-19 at 3.58.40 pm

Basic about HTML5 banner

What is HTML5 Banner? – they are pieces of HTML5 that display advertisements.

The Banner HTML5 Format is a standard in-page ad that works on every platform that supports HTML5 technology (PC, cellphone, tablet, etc.). It can contain video or audio.

Why do we need them? – in the past, most of advertisements are Flash, because they are small and animation rich. But Flash is no longer supported by major browsers and will soon become a dead technology. HTML5 banner emerges as a replacement.

What’s the differences between HTML5 banner and regular HTML webpage? – HTML5 banner, like Flash ad, is a standalone piece, which is running separately from its host pages with its own resources, while regular HTML webpage is a part of the site content and share the same resources with all other pages. Because of this characteristics, HTML5 banner can be deployed at any place as a self-contained media.

What makes up of a HTML5 banner? – No alien languages, they are just HTML5, CSS, Javascript. Anything fancy would be considered dangerous since some browsers may not support them. We want the ads to run at most places it can so the technologies must be simple enough and well-supported.

Specification of HTML5 Banner

The specification might be lengthy, fully described here. This document covers all best practices one should follow to develop a HTML5 banner piece that is up to industrial standard.

TL;DR – The deliverables of a HTML5 banner include:

  • ZIP file containing:
    • index.html main file
    • Javascript, CSS files used in it
    • External media files used in it (Images, videos, sounds, XML, etc.).
  • Backup image

The zip file size must not exceed 100kb, and for the backup image, it must not exceed 40kb. In fact, the size of the resources does not really matter since they can be ziped upon transport and cached. But there is an industrial requirement anyway.

Looks complicated enough, so how do you actually creat a HTML5 ad banner?

Create a HTml5 ad banner

A piece of HTML5 ad banner often consist of following:

  • A pre-defined size: 150×100, 300×250, 720×90, etc. they are standard.
  • A collection of images, videos, audios
  • A series of animation that needs to play from start to end

A file structure might look like below:
Screen Shot 2015-09-19 at 3.10.52 pm

This structure very well deals with the elements above:

  • app.css – specify dimension, position and other attributes of every element in the Ad. CSS for HTML5 banner does not need to be beautiful, it just needs to be concise and fast.
  • app.js – contains all the code to control the animations.
  • sizzle.min.js – the selector engine used by jQuery, you do not need the whole jQuery bundle due to lack of space.
  • TweenMax & TimelineMax – a robust library to perform animation, it is small, cross-browser and high performance

The first file we need to create is the index.html

<!DOCTYPE html> 
<html>
<head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 
   <title>GSAP animation</title>
   <link rel="stylesheet" href="app.css"/>
   <script src="TweenLite.min.js"></script>
   <script src="TimelineMax.min.js"></script>
   <script src="easing/EasePack.min.js"></script>
   <script src="plugins/CSSPlugin.min.js"></script>
   <script src="sizzle.min.js"></script>
   <script src="share.js"></script>
   <script src="app.js"></script>
</head>
<body> 
   <a id="myAdLink" href="http://www.example.com"> 
      <div id="myAd">
        <div id="frame1" class="bn"></div>
        <div id="frame2" class="bn"></div>
        <div id="frame3" class="bn"></div>
        <div id="frame4" class="bn"></div>
        <div id="frame5" class="bn"></div>
        <div id="frame6" class="bn"></div>
      </div> 
   </a>
</body>
</html>

All elements are there but hidden from view. It is different from normal approach that generate DOM elements based on needed. HTML5 Ad Banner focuses on visual effects, not DOM efficiency. So everything is there we just need to manipulate them and ignore complexity of adding/removing elements.

We will specify visual attributes of the elements in CSS file as below:

a#myAdLink { display: inline-block; background-color: #2480d6; }
#myAd { width: 300px; height: 250px; background-color: #f28023; border: 1px solid #ccc; position: relative; overflow: hidden; }
#myAd img { position: absolute; top: -90px; }
.bn {
  position: absolute;
  left: 0;
  top: -250px;
  width: 300px;
  height: 250px;
  background-repeat: no-repeat;
  background-position: 0 0;
}
#frame1 {
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANS...SuQmCC');
}
#frame2 {
  background-image: url('data:image/png;base64,iVBORw0KGg...5CYII=');
}

As you may notice, the images are encoded into base64 string. Reason? Because of:

  • All resources must be local to the html file
  • Images are available immediately, it is bad if the animate starts but images aren’t fully loaded yet.

Now, it’s time to start manipulating the frames. We will start with #frame1 till the last frame.

window.onload = function(){
    var f1 = $('#frame1'),
        f2 = $('#frame2'),
        f3 = $('#frame3'),
        f4 = $('#frame4'),
        f5 = $('#frame5'),
        f6 = $('#frame6');

    var tl = new TimelineMax();
    // Copy drops from top down and bounce, stays for 2 seconds then disappear
    tl.to(f1, 2, { y: 250, opacity: 1, ease: Bounce.easeOut }) 
      .stay(f1)
      .to(f1, 0.5, { y: -250, opacity: 0, ease: Power1.easeIn });

    // Copy drops from top down, stays for 2 seconds and ease out
    tl.to(f2, 2, { y: 250, opacity: 1, ease: Bounce.easeOut })
      .stay(f2)
      .to(f2, 0.5, { y: -250, opacity: 0, ease: Power1.easeIn });
....

Regarding the animation, there will be a lot of animation libraries out there to choose from. Greensock was chosen here because it is small, animation packed and solid. If you want to display your Ad on Canvas, perhaps you should choose something like Raphael.

After all hard work done, you can open your ad from index.html in any browswer and enjoy the result!

P.S: alternatively you can use online services like html5maker to produce the ads, but you won’t have full control over the code and it is easy to get stucked.

thethanghn🙂

How to link to your local nodejs module from your Javascript project

It is not always not obvious to do this, that’s why there is this note:

1. Build your local module

Go to the root folder of your module (where the file package.json is) and run this:

$ npm run-script build-npm

This command will push your files into output folders, which can be referenced by comsuming project.

2. Update your project package.json

It is similar to Rails, you can refer to local module as well as Git module, like below:

{
  "name": "myproject",
  "version": "0.2.0",
  ...
  "dependencies": {
    "@thethanghn/react-bootstrap-datetimepicker": "file:/Users/justin/react-bootstrap-datetimepicker",
    ...
  }
}

You may notice I used a scoped name, because this module is forked from already published module on Npm.

3. Install the module

Now, everything is ready to go, we can install the module specifically:

$ npm install @thethanghn/react-bootstrap-datetimepicker

Notice that we install the module along with its scope, since it is not the original module. With this, we can update our module over and over to make sure everything works well, then we can go to the next step.

4. Publish your module if you want

Publishing our work could be coool! But it also to allow other people to use the module, or allow server to download it. You will need to publish the module to npm to allow online access.

$ npm publish

Then, we are ready to use the online version instead of our local one:

{
  "dependencies": {
    "@thethanghn/react-bootstrap-datetimepicker": "^0.0.21",
    ...
  }
}

That’s it!

Introduction about Mocha.js

Mocha

Mocha is a feature-rich JavaScript test framework running on Node.js and the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases.

Test case example

It is a full-featured test frameworks, i.e. it covers everything that a test framwork should have. It may be lengthy and unnecessary to describe its features, so let’s have a look at this test file:

describe('Array', function() {
    before(function() {
      // ...
    });

    describe('#indexOf()', function() {
      context('when not present', function() {
        it('should not throw an error', function() {
          (function() {
            [1,2,3].indexOf(4);
          }).should.not.throw();
        });
        it('should return -1', function() {
          [1,2,3].indexOf(4).should.equal(-1);
        });
      });
      context('when present', function() {
        it('should return the index where the element first appears in the array', function() {
          [1,2,3].indexOf(3).should.equal(2);
        });
      });
    });
  });

A sample output:

Ajax & promise handling

Mocha makes it easy to write test spec for these operations.

Asynchronous callback

describe('User', function() {
  describe('#save()', function() {
    it('should save without error', function(done) {
      var user = new User('Luna');
      user.save(done);
    });
  });
});

`done` is a special callback provided by Mocha so that it knows when the test case finishes.

Promise

`promise` is a new convention for asynchronous operation, it simplifies the way to handle output of the operation, therefore it grows fast in popularity.

beforeEach(function() {
  return db.clear()
    .then(function() {
      return db.save([tobi, loki, jane]);
    });
});

describe('#find()', function() {
  it('respond with matching records', function() {
    return db.find({ type: 'User' }).should.eventually.have.length(3);
  });
});

Assertions

One thing to note that Mocha does not provide an assertion library, it rathers use node’s assert module or following:

  • should.js BDD style shown throughout these docs
  • expect.js expect() style assertions
  • chai expect(), assert() and should style assertions
  • better-assert c-style self-documenting assert()

They all are capable of performing necessary assertions so the decision pretty much depends on your coding style.

In conclusion, Mocha is a feature-rich test framework with very simple setup and you can immediately put it into use. It supports various coding styles so that if you are already familiar with one of famous testing frameworks you may find yourself at home when you use Mocha.

🙂

Introduction about Karma.js

We have known about Webpack, let’s explore another great tool: Karma (a great name!)

Karma is essentially a tool which spawns a web server that executes source code against test code for each of the browsers connected. The results for each test against each browser are examined and displayed via the command line to the developer such that they can see which browsers and tests passed or failed.

What is good about Karma?

Well then Karma can do following things for you:

  • Starts a web server
  • Executes test AGAIN for every connected browser
  • Shows test result out put in console

To be able to launch a browser is important, because it allows us to access real DOM and take screenshots.

Samples

Let’s have a look at a simple Karma configuration file:

module.exports = function(config) {
 config.set({
   basePath: 'js',

   files: [
     'vendor/jquery/jquery.min.js',
     'vendor/handlebars/handlebars.js',
     'vendor/ember/ember.js',
     'app.js',
     'tests/*.js',
     'templates/*.handlebars'
   ],

   browsers: ['PhantomJS'],
   singleRun: true,
   autoWatch: false,

   frameworks: ['qunit'],

   plugins: [
     'karma-qunit',
     'karma-ember-preprocessor',
     'karma-phantomjs-launcher'
   ],

   preprocessors: {
     '**/*.handlebars': 'ember'
   }
 });
};

Or we can even make a combination with Webpack:

module.exports = function(config) {
  config.set({
    frameworks: ['mocha', "chai"],
    files: [
      'src/spec/**/*.spec.*'
    ],
    webpack: {
      module: {
        loaders: [{
          test: /\.jsx$|\.js$/,
          loaders: ['babel']
        }]
      }
    },
    preprocessors: {
      'src/spec/**/*.spec.js': ['webpack']
    },
    plugins: [
      require('karma-webpack'),
      require('karma-chai'),
      require('karma-mocha')
    ]
  });
};

As you can see from the 2nd example, with the power of webpack, we don’t need to include so many files in at files section.

Why should we use Karma?

In general, Karma has similar features like Webpack: preprocessors, plugins, loaders, etc. (maybe they learnt from each other) The strength of Karma lies in its abilities to run test code on different browsers and gives out output. This output can subsequently be used in reporting service like: Travis CI, Jenkins CI, etc. it also supports famous test frameworks like Mocha and Qunit. With Karma running in background, you are confident to write quality code🙂

Introduction about Webpack.js

Since the emergence of Nodejs, JavaScript tools have moved from merely client helper libraries into a totally new territory called server side. That’s why there is a boom of javascript tools lately and we are going to look into some of them. First begin with Webpack.

Webpack

// webpack is a module bundler
// This means webpack takes modules with dependencies
// and emits static assets representing those modules.

For example, in .NET, when we press `publish`, the source code gets compiled and moved to another folder, ready for us to deploy it to production server. It is exactly the job that Webpack does.

Something like this can only appear when server-side javascript becomes available, back then, it was handled by other server-side languages like C#, Ruby, etc. now we got it right in Javascript.

Here is a diagram illustrating how it works:

Why Webpack is useful?

Webpack does not merely copy things around, it does following things for us in the process:

  • Bundles everything into a single file
  • Compiles different file types into vanilla JavaScript, e.g. coffee to js
  • Auto-handle library dependencies
  • Supports various optimazation
  • Shipped with a small `webpack-dev-server` for us to run the bundle immediately

So, we can say that with Webpack, we have more freedom to write the code without having to think so much about optimizing file structure.

Some other cool things about Webpack you should know

Get Started
Webpack is very easy to get started with, following this tutorial, you can easily transform your page into an application.

Loaders

Loader is the tool for Webpack to know about a file type and the way to process it, i.e. for each file type there will be a respective loader. Without loaders, Webpack cannot know how to compile CoffeeScript or TypeScript into JavaScript. The list of loaders is very long, but it supports all the major stuff, for example: handlerbars and jsx.

Dependencies

Handling dependencies is one of important reasons to choose Webpack, we expect that after going through bundling process, everything still work smoothly. Webpack can work with Requirejs, CommonJs, or even inine javascript inclusion.

Code Splitting

Bundles everything into a single javascript file is not always a good thing, such as in very big application. Thus, Webpack supports Code Splitting that is to split the bundle into smaller chunks which can be loaded separately or on demand. Well, it is … useful in some certain scenarios.

Plugins

Plugins are for intercepting with bundling process and inject your own commands, so you can freely control the output at every step.

Webpack is a huge tool and there are still a lot more things we can talk about, but let’s try to use it and you will find how convenient it is for project deployment.

🙂

A journey with ReactJs: Global Dispatcher

In our previous example, we have learnt about how to send events across components by using the common root component as the transporter. Now, let’s stretch this topic a little bit more!

In most cases, our pages are not as simple as a few components with some levels of depth. They can become much more complex and have changing structure depends on who the user is and how they interact. That makes the communication via common root component become very difficult and there are alot of event delegation throughout the app.

There may be different ways to tackle this issue, let’s have a look at this diagram from Facebook’s Flux architecture:

This diagram demonstrates a rather broad concept, that may be too much for us, as we are at the beginner level (I don’t understand the diagram neither). The idea is to that instead of components sending events to each other, we make them send to a global dispatcher, and then any component that is listening on that event will receive this event.

ReactJS Dispatcher

Let’s reuse our last example (because writing dummy boxes is kind of boring :3 ) https://jsfiddle.net/69z2wepo/11700/, we are going to add a Category listing on the left, so when we change the Category, the news changes too.


var Category = React.createClass({
    render: function() {
        return <ul className="categories">
            <li>Breaking News</li>
            <li>Business</li>
            <li>Sport</li>
        </ul>;
    }
});

var App = React.createClass({
    render: function() {
        return <div className="app-layout">
              <Category/>
              <TopNews source={this.props.source} />
          </div>;
    }
});
 
React.render(<App source={source}/>, document.getElementById('container'));

https://jsfiddle.net/69z2wepo/12024/

Next, let’s add event handler when we click on our category.

var Category = React.createClass({
    clickHandler: function(ev) {
        console.log(ev);
    },
    render: function() {
        return <ul className="categories">
            <li data-cat="breaking-news" onClick={this.clickHandler}>Breaking News</li>
            <li data-cat="business" onClick={this.clickHandler}>Business</li>
            <li data-cat="sport" onClick={this.clickHandler}>Sport</li>
        </ul>;
    }
});

Now, our dispatcher should get into play. We are going to create this object and it should have following abilities:

  • Allow registering for event handler
  • Determine which handler interests in which component’s event
  • Able to broadcast events to subscriber

To do so, our dispatcher needs to keep track of events by components and by subscribers, we change the code as follow:

var Category = React.createClass({
    clickHandler: function(ev) {
        var category = $(ev.target).data('cat');
        Dispatcher.publishEvent('Category', 'click', { category: category });
    },
    render: function() {
        return <ul className="categories">
            <li data-cat="breaking-news" onClick={this.clickHandler}>Breaking News</li>
            <li data-cat="business" onClick={this.clickHandler}>Business</li>
            <li data-cat="sports" onClick={this.clickHandler}>Sport</li>
        </ul>;
    }
});

var SourceListener = {
    categoryChanged: function(data) {
        console.log('categoryChanged');
    }
};

Dispatcher.register('Category', 'click', SourceListener.categoryChanged);

The reason that we used a SourceListener object here is because we known from previous example that our component should be encapsulated, i.e. we should not expose its details, so we are unable to write something like TopNews.categoryChanged. What we need to manipulate is the source of truth, that is the source object which contains all the data to render components.

So the code for Dispatcher to lookup subscriber is straightforward:

var Dispatcher = (function(){
    this.subscribers = [];
    
    var self = this;
    return {
        publishEvent: function(componentName, eventName, data) {
            console.log('registering event');
            var subscribers = self.subscribers.filter(function(subscriber) {
                return subscriber.component == componentName && subscriber.event == eventName;
            });
            for(var i = 0; i < subscribers.length; i++) {
                subscribers[i].callback.call(data);
            }
        },
        register: function(componentName, eventName, callback) {
            self.subscribers.push({component: componentName, event: eventName, callback: callback});
        }
    };
})();

https://jsfiddle.net/69z2wepo/12029/

Let’s update our datasource to reflect the category structure and wait to see what happens.

var source = [
    { id: 1, img: 'http://c1.f2.img.vnecdn.net/2015/07/08/ca-2635-1436322583_180x108.jpg', text: 'Đồng đội ủng hộ Casillas bỏ Real để đến Porto', url: '#', category: 'sports' },
    { id: 2, img: 'http://c1.f3.img.vnecdn.net/2015/07/08/pir-7443-1436321877_180x108.jpg', text: 'Lampard không dám tranh đá phạt với Pirlo', url: '#', category: 'sports' },
    { id: 3, img: 'http://c1.f2.img.vnecdn.net/2015/07/07/3-1436263678_180x108.jpg', text: 'Dàn vệ sĩ tháp tùng Abramovich đi thăm đảo', url: '#', category: 'sports' },
    { id: 4, img: 'http://c1.f4.img.vnecdn.net/2015/07/08/turan-4047-1436317374_180x108.jpg', text: 'Ứng cử viên Chủ tịch Barca thề ném Turan trở lại Atletico', url: '#', category: 'sports' },
    { id: 5, img: 'http://c1.f3.img.vnecdn.net/2015/07/08/cabye-7263-1436323529_180x108.jpg', text: 'Yohan Cabaye trở lại Ngoại hạng Anh', url: '#', category: 'sports' },
    { id: 6, img: 'http://c1.f9.img.vnecdn.net/2015/07/14/ngo-c-trinh-phoi-do-den-trang-nu-tinh-1436847994_180x108.jpg', text: 'Ngọc Trinh phối đồ đen trắng nữ tính', url: '#', category: 'breaking-news' },
    { id: 7, img: 'http://c1.f10.img.vnecdn.net/2015/07/13/50899835-anh-thu-11-4538-1436760636_180x108.jpg', text: 'Phá cách áo dài không khéo sẽ thành thảm họa văn hoá', url: '#', category: 'breaking-news' },
    { id: 8, img: 'http://c1.f12.img.vnecdn.net/2015/07/13/a-hau-huyen-my-khoe-gu-thoi-trang-o-my-1436778696_180x108.jpg', text: 'Á hậu Huyền My dạo chơi tại Mỹ', url: '#', category: 'breaking-news' },
    { id: 9, img: 'http://c1.f28.img.vnecdn.net/2015/07/14/micron-1724-1436839428_180x108.jpg', text: 'Hãng chip Trung Quốc muốn chi 23 tỷ USD mua Micron', url: '#', category: 'business' },
    { id: 10, img: 'http://c1.f27.img.vnecdn.net/2015/07/13/13-7-201539-472259864-3640-1436779228_180x108.jpeg', text: 'Mở bán những căn hộ hạng sang Park Premier cuối cùng', url: '#', category: 'business' },
];

var SourceListener = {
    categoryChanged: function(data) {
        React.render(<App source={source.filter(function(item){return item.category == data.category;})}/>, document.getElementById('container'));
    }
};

https://jsfiddle.net/69z2wepo/12030/

Screen Shot 2015-07-14 at 5.29.45 pm

It is pretty much finished our example, many questions may arise from this, such as dependency injection. Actually all those were covered by Flux so I won’t go on explaining about it. In summary, we have achieved following:

  • Decoupled event handling between components
  • Implemented a sample global dispatcher
  • Dynamically changed different parts of the UI

Acceptance Test Robot Framework: Test returned JSON by Rest API

API testing is one of common jobs in automation tests, especially when multiple teams work on different parts of a system and use API as the mean of communication between components.

We will examine how Robot Framework can help us to test the API, let’s say REST API that return JSON results.

To access REST API, we need to use a HTTP Library. Standard Libriries from Robot Framework does not include this, so we need to manually into this addon.

pip install --upgrade robotframework-httplibrary

Then, let’s get started to test our API!

Wait, where is our API to test? We don’t have one, but luckily enough, there is free service to build sample APIs that we can use apiary.io, it is a great tool to mock API calls, i.e. you can trap the call requests and return any dummy data you want without writing any real logic. In short, no coding required!

Just register then it gives us a default API sample that we can use in our test immediately:

//http://private-2012e-nguyenthethang.apiary-mock.com/questions?page=1

[  
   {  
      "published_at":"2015-07-10T05:49:29.577344+00:00",
      "choices":[  
         {  
            "url":"/questions/62/choices/265",
            "votes":0,
            "choice":"Android"
         },
         {  
            "url":"/questions/62/choices/264",
            "votes":0,
            "choice":"Bot"
         },
         {  
            "url":"/questions/62/choices/266",
            "votes":0,
            "choice":"God"
         },
         {  
            "url":"/questions/62/choices/263",
            "votes":0,
            "choice":"Human"
         }
      ],
      "url":"/questions/62",
      "question":"Who Are You?"
   },
...
]

Let’s create our test file api.robot. We start by importing the library and make the very first test: make sure our request will return a successful status (status=200)

*** Settings ***
Documentation                 Test our very first REST API
Library                       HttpLibrary.HTTP
*** Variables ***
${API_ENDPOINT}               http://private-2012e-nguyenthethang.apiary-mock.com

*** Test Cases ***
List Question Request Should Return Successful
  GET                                   ${API_ENDPOINT}/questions
  Response Status Code Should Equal     200

Now, run our console to check result:

pybot api.robot
==============================================================================
Api :: Test our very first REST API
==============================================================================
List Question Request Should Return Successful                        | PASS |
------------------------------------------------------------------------------
Api :: Test our very first REST API                                   | PASS |
1 critical test, 1 passed, 0 failed
1 test total, 1 passed, 0 failed
==============================================================================
Output:  /Users/justin/robotframework/APITest/output.xml
Log:     /Users/justin/robotframework/APITest/log.html
Report:  /Users/justin/robotframework/APITest/report.html

Next, we will write some tests to verify the content of the API response. Let’s check if the response contains the correct number of items (1 item in the mock data, we can create more if we want). It takes several steps to do so:

  • Grab the response result
  • Convert it into JSON
  • Examine the JSON object

Here is our test content:

List Question Requset Should Return Correct Number Of Items
  GET                                   ${API_ENDPOINT}/questions
  ${result} =         Get Response Body
  ${json} =           Parse Json                            ${result}
  Length Should Be    ${json}                               10

That’s good. Our tests pass beautifully. But we should refactor it a little bit so that it looks nicer and descriptive enough.

*** Settings ***
Documentation                 Test our very first REST API
Library                       HttpLibrary.HTTP
*** Variables ***
${API_ENDPOINT}               http://private-2012e-nguyenthethang.apiary-mock.com

*** Test Cases ***
List Question Request Should Return Successful
  Get Question list
  Response Status Code Should Equal     200

List Question Requset Should Return Correct Number Of Items
  Get Question list
  ${json} =           Parse Response Body To Json
  Length Should Be    ${json}                               10

*** Keywords ***
Get Question list
  GET                                   ${API_ENDPOINT}/questions

Parse Response Body To Json
  ${result} =         Get Response Body
  ${json} =           Parse Json                            ${result}
  [Return]            ${json}

So we have DRYed it a bit and know that a keyword can return data. Next, let’s try to make a POST request, it is also a very common case.

You can find an API to create question at apiary editor, according to it, we need to send following JSON data to server:

{
  "question": "Favourite programming language?",
  "choices": [
    "Swift",
    "Python",
    "Objective-C",
    "Ruby"
  ]
} 

This API will return the created question detail as response, so we not only test if it is 200, but also double check the result.

*** Settings ***
Documentation                 Test our very first REST API
Library                       HttpLibrary.HTTP

*** Variables ***
${API_ENDPOINT}               http://private-2012e-nguyenthethang.apiary-mock.com
${QUESTION_JSON_DATA}         { "question": "Favourite programming language?", "choices": [ "Swift", "Python", "Objective-C", "Ruby" ] }

*** Test Cases ***
Create Question Should Return Success
  Create Http Context                   private-2012e-nguyenthethang.apiary-mock.com    http
  Set Request Header                    Content-Type          application/json
  Set Request Body                      ${QUESTION_JSON_DATA}
  POST                                  /questions
  Response Status Code Should Equal     200

Code is self-explanatory: we set the necessary headers and body and send it, it would return desired 200 status. We want to compare the returned result with our expected result, particular this response:

{
    "question": "Favourite programming language?",
    "published_at": "2014-11-11T08:40:51.620Z",
    "url": "/questions/2",
    "choices": [
        {
            "choice": "Swift",
            "url": "/questions/2/choices/1",
            "votes": 0
        }, {
            "choice": "Python",
            "url": "/questions/2/choices/2",
            "votes": 0
        }, {
            "choice": "Objective-C",
            "url": "/questions/2/choices/3",
            "votes": 0
        }, {
            "choice": "Ruby",
            "url": "/questions/2/choices/4",
            "votes": 0
        }
    ]
}

But this content is rather long, if we put it in our test file, it would be a little bit messy, therefore we will put it into an external file and read it into our test case. To do that, we import the library OperatingSystem from Standard Libraries.

*** Settings ***
Documentation                 Test our very first REST API
Library                       HttpLibrary.HTTP
Library                       OperatingSystem

*** Test Cases ***
Create Question Should Return Success
  Create Http Context                   private-2012e-nguyenthethang.apiary-mock.com    http
  Set Request Header                    Content-Type          application/json
  Set Request Body                      ${QUESTION_JSON_DATA}
  POST                                  /questions
  Response Status Code Should Equal     200
  ${result} =                           Parse Response Body To Json
  ${expectation} =                      Parse Json From File 
  Should Be Equal                       ${result}                ${expectation}

*** Keywords ***
Parse Json From File
  ${file} =                             Get File    question_detail.json
  ${json} =                             Parse Json    ${file}
  [Return]                              ${json}

Note that Apiary supports multiple mode, in Production mode, your requests can change data on server. So far we should always use Mock Server mode, so that it returns what we config it to be. Otherwise the test case maybe much more complex than the scope of this post. Such as primary key value and timestamp values always change and difficult to test.

You may notice that we parse Json into objects and compare them. It is pretty difficult to compare 2 json objects somehow.

In the end, we have done our important test cases, let’s refactor the code so that it looks tidier.

*** Settings ***
Documentation                 Test our very first REST API
Library                       HttpLibrary.HTTP
Library                       OperatingSystem
Test Setup                    Create API Context

*** Variables ***
${API_ENDPOINT}               http://private-2012e-nguyenthethang.apiary-mock.com
${QUESTION_JSON_DATA}         { "question": "Favourite programming language?", "choices": [ "Swift", "Python", "Objective-C", "Ruby" ] }

*** Test Cases ***
List Question Request Should Return Successful
  Get Question list
  Response Status Code Should Equal     200

List Question Requset Should Return Correct Number Of Items
  Get Question list
  Question List Should Have Correct Length
  

Create Question Should Return Success
  Make Question Creation Request
  Response Status Code Should Equal     201
  Created Question Details Should Be Correct

*** Keywords ***
Create API Context
  Create Http Context                   private-2012e-nguyenthethang.apiary-mock.com    http

Make Question Creation Request
  Set Request Header                    Content-Type          application/json
  Set Request Body                      ${QUESTION_JSON_DATA}
  POST                                  /questions

Question List Should Have Correct Length
  ${json} =                             Parse Response Body To Json
  Length Should Be                      ${json}                               1

Created Question Details Should Be Correct
  ${result} =                           Parse Response Body To Json
  ${expectation} =                      Parse Json From File
  Should Be Equal                       ${result}                ${expectation}

Get Question list
  GET                                   /questions

Parse Json From File
  ${file} =                             Get File    question_detail.json
  ${json} =                             Parse Json    ${file}
  [Return]                              ${json}

Parse Response Body To Json
  ${result} =                           Get Response Body
  Log                                   ${result}
  ${json} =                             Parse Json                            ${result}
  [Return]                              ${json}

By moving the techinical details into keywords we make our test cases more decriptive as natural languages. Just by reading the test cases, people can easily grasp what we are trying to prove. If a long time later we come back and look at the tests, we still understand it clearly and easily.

Summary, this post demonstrates:

  1. Ability of Robot Framework to test Rest API
  2. Ability to read content from file
  3. Encode/decode JSON content
  4. Usage of Apiary as mock server