Giter Site home page Giter Site logo

api-easy's Introduction

APIeasy Build Status

A fluent (i.e. chainable) syntax for generating vows tests against RESTful APIs.

Installation

Installing npm (node package manager)

  $ curl http://npmjs.org/install.sh | sh

Installing APIeasy

  $ [sudo] npm install api-easy

Purpose

APIeasy is designed to be a simple way to test RESTful APIs in node.js and Javascript. The primary design goal was to reduce the number of lines of test code required to fully cover all primary and edge use cases of a given API over HTTP.

Getting Started

Most of the documentation for this library is available through the annotated source code, available here thanks to jashkenas and docco. If you're not feeling up for that, just keep reading here. tldr;? Read how to use APIeasy in your own projects

If you're going to use APIeasy (and I hope you do), it's worth taking a moment to understand the way that vows manages flow control. Read up here on vowsjs.org (Under "Structure of a test suite"), or just remember vows uses this grammatical structure:

  Suite   → Batch*
  Batch   → Context*
  Context → Topic? Vow* Context*

Got it? Good. There is a 1-to-1 relationship between a APIeasy suite and a vows suite; APIeasy is essentially a simpler syntax to manage a particular set of vows-based tests that conform to this pattern:

  1. Tests are performed by making HTTP requests against an API server
  2. Assertions are made against the HTTP response and JSON response body
  3. Rinse. Repeat.

Here's a sample of the boilerplate code that APIeasy eliminates:

  var request = require('request'),
      vows = require('vows'),
      assert = require('assert');

  vows.describe('your/awesome/api').addBatch({
    "When using your awesome api": {
      "and your awesome resource": {
        "A POST to /awesome": {
          topic: function () {
            request({
              uri: 'http://localhost:8080/awesome',
              method: 'POST',
              body: JSON.stringify({ test: 'data' }),
              headers: {
                'Content-Type': 'application/json'
              }
            }, this.callback)
          },
          "should respond with 200": function (err, res, body) {
            assert.equal(res.statusCode, 200);
          },
          "should respond with ok": function (err, res, body) {
            var result = JSON.parse(body);
            assert.equal(result.ok, true);
          },
          "should respond with x-test-header": function (err, res, body) {
            assert.include(res.headers, 'x-test-header');
          }
        }
      }
    }
  }).export(module);

This same code can be implemented like this using APIeasy:

  var APIeasy = require('api-easy'),
      assert = require('assert');

  var suite = APIeasy.describe('your/awesome/api');

  suite.discuss('When using your awesome API')
       .discuss('and your awesome resource')
       .use('localhost', 8080)
       .setHeader('Content-Type', 'application/json')
       .post('/awesome', { test: 'data' })
         .expect(200, { ok: true })
         .expect('should respond with x-test-header', function (err, res, body) {
           assert.include(res.headers, 'x-test-header');
         })
       .export(module);
## Using APIeasy in your own project There are two ways to use APIeasy in your own project:
  1. Using npm
  2. Using vows directly

If you've used the npm test command in npm before, this should be nothing new. You can read more about the npm test command here. All you need to do is add the following to your package.json file:

 {
   "dependencies": {
     "api-easy": "0.2.x"
   },
   "scripts": {
     "test": "vows test/*-test.js"
   }
 }

Note: test/*-test.js is at your discretion. It's just an expression for all test files in your project.

After adding this to your package.json file you can run the following to execute your tests:

  $ cd path/to/your/project
  $ npm install
  $ npm test

There is also a full working sample of how to use this approach here.

Using APIeasy with vows

When you install APIeasy or take it as a dependency in your package.json file it will not install vows globally, so to use vows you must install it globally.

  $ [sudo] npm install vows -g

After installing vows you can simply run it from inside your project:

  $ cd /path/to/your/project
  $ vows

Roadmap

  1. Get feedback on what else could be exposed through this library.
  2. Improve it.
  3. Repeat (1) + (2).

Run Tests

  npm test

api-easy's People

Contributors

coderarity avatar indexzero avatar marak avatar maxired avatar mmalecki avatar n8d avatar outbounder avatar pbouzakis avatar pksunkara avatar ppavel avatar purge avatar ress avatar rodrigok avatar stanley avatar ypomortsev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

api-easy's Issues

i get error ECONNREFUSED.

As i run the test with vow, i get error ECONNREFUSED.

error

i am using node.js & run the vow with following command.
vows test.js.

How to solve this issue?

A POST to a page that redirects causes another POST to the redirected page

I hope that makes sense.

I have a test that attempts to contact my REST service and submit a form, approximately like the following:

suite
    .use('localhost', 3000)
    .setHeader('Content-Type', 'application/json')
    .discuss('When using the Thread API')
        .post('/thread', {title: 'title-test', author: 'John Doe'})
            .expect(302)
            .next()

And the request handler under test (an Express method):

app.post('/thread', function(req, res) {
    var newThread = new Thread({title: req.body.title, author: req.body.author});
    newThread.save();
    res.redirect('home');
});

When calling this through a browser or CURL, I get a neat '302' message. However, when trying this in api-easy, I get the following response:

  When using the Thread API A POST to /thread
    ? should respond with 302
      » expected 302,
        got      404 (==) // api-easy.js:233

I went debugging and replaced my expect() with one that prints out the error, response and body contents, and the body content in particular seems to highlight the problem:

Cannot POST /

(where '/' is the homepage redirected to). It seems api-easy, or the library it uses to run POST-requests, re-uses the same request type when getting redirected after a post.

So, a few questions:

  • Can this be fixed, or:
  • Am I doing redirects wrong?
  • Am I testing wrong?

Failed to get the right status code?

I did a http server like this:

var http = require('http');

http.createServer(function( req, res ) {
    res.writeHead(302, {
        'Content-Type' : 'text/html',
        'Location' : 'http://www.google.com'
    });
    res.write('Yay');
    res.end();
}).listen(9000);

and a test case like this:

var ApiEasy = require('api-easy');

var suite = ApiEasy.describe('Check if server is alive');

suite.discuss('GIVEN that I want to check the server\'s basic REST API')
    .use('localhost', 9000)
    .path('/')
        .get()
        .expect(302);

suite.export(module);

But it shows:

should respond with 302
» expected 302,
got 200 (==)

Should we embedded one suite in another suite?

The codes are as bellows.

suite.discuss('...')
....
.post(...)
.expect('got the event', function(err, res, body){
suite1.discuss('....')
})

and suite1 is embedded in the suite, I want to know whether it is OK?

Permission denied when running the test

Hi,

I've tried to follow the instructions, but I keep on getting a "Permission denied" error:

Macintosh:foo tito$ npm test
npm WARN package.json [email protected] No repository field.
npm WARN package.json [email protected] No readme data.

> [email protected] test /Users/tito/Desktop/FooDevelopment/foo
> tests/*-test.js

sh: tests/connect-test.js: Permission denied
npm ERR! weird error 126
npm ERR! not ok code 0
Macintosh:foo tito$ 

I'm not sure what's wrong. This is what tests/connect-test.js looks like:

var APIeasy = require('api-easy');
var suite = APIeasy.describe('your/awesome/api');

suite.discuss('When using your awesome API')
  .discuss('and your awesome resource')
  .use('localhost', 8080)
  .setHeader('Content-Type', 'application/json')
  .post('/connection')
  .expect(200)
.export(module);

The permissions look like this

-rw-r--r--   1 tito  staff  2311 Jul  7 20:39 connect-test.js

Any ideas? Thanks!

`run-test` is failing

I noticed this while pulling in the s/querystring/qs/ pull request; however, the failing tests have little/nothing to do with it.

'secure' losing query string?

My tests against https fail with the message that indicates the query string isn't being passed.
The api requires a 'token' in the querystring.

The following call against HTTPS fails with a 403:

suite.use('api.example.com', 443, {secure:true})
     .get('/accounts',{token:apitoken})
       .expect(200, { ok: true })
     .export(module);

But this code against HTTP works and returns a 200:

suite.use('api.example.com', 80)
     .get('/accounts',{token:apitoken})
       .expect(200, { ok: true })
     .export(module);

Am I just trying to do HTTPS wrong?

How to get the redirect url and reuse this url to do get operation?

hi Charlie,

When I do a post operation and want to get the new url information from redirection. so I can do the new get operation.
I do not how to do that? Could you please help me?

code:
suite.discuss('now we create a project')
.use('localhost', 3000)
.setHeader('Content-Type', 'application/json')
.followRedirect(false)
.path('/projects')
.post( { information : 'good news'})
.expect(302)
.expect('redirect content', function(err, res, body){
...

I know that I can get the url from res.headers.location, but how can I do a get operation with this url??

why strip the trailing slash?

This is no big deal, but sometimes a slash is needed to get a directory. That's what c9/vfs (the vfs-http-adapter) currently requires.

Unable to stall tests until connections are made

For my tests, although they rely on mongoose I'd like them not to run until something happens (in this case, mongoose triggers the "open" event.)

Although I use mongoose in this example, and blocking operations are a flamewar-earning topic in node.js, I think my question still stands.

At present there's no way to postpone the wrap the tests firing into a callback, and I think that'd be very useful. Even something like a setup method that receives a callback function and tests won't proceed until the callback is triggered.

How could I handle body, when I use res.render() pages?

Server side code:

res.render('dashboard', {
      layout: 'layouts/default'
      , user: req.currentUser
}

api-easy test code

.get(urlRedir)
.expect(200)
.expect('get user infor', function(err, res, body){

in the body, there are whole html element. Is there any better method to read it?

Using data from one test in subsequent tests

I have the need to use data gathered from one test in a subsequent test, and so far I've not found a convenient way to do this.

The use case I'd like to support is something like:

POST /myresource - this creates a new resource and returns identifier X

PUT /myresource/X - this updates the resource

I understand that I can use .next() to have on test run after the other, however as all the methods are chained together, parameters are evaluated before the tests run, not after. I have worked around this for now using a .before() function which finds tokens in the URL and request body and replaces them with values. This works, but I suspect there is a better way.

offtopic: why do you parse params like this

I'm having a look at your code, and I'm learning quite a few js tricks :-)

but I don't get why you issue

use: function (host /* [port, options] */) {
      var args = Array.prototype.slice.call(arguments),
          options = typeof args[args.length - 1] === 'object' ? args.pop() : {},
          port = args[1];

instead of just

use: function (host, port, options) {
  options = options || {};

In other places I see you use that tecnique to allow for a more flexible way of passing parameters...

ps: sorry for the offtopic question

No way to tests API over SSL

There is no way to configure the .use() directive to use https.
I have tryied with host and port 443 without success.

There's no way to execute something (like DB population) before and after each test

I'd be good to add a way to execute some hooks like setup, before and after each test and a tear down after all the tests, something like this:

suite.discuss('.....').use('localhost'. 3001)
  .setup(function(){
     // This should be executed once before starting with the tests
  })
  .beforeEach(function(){
      // This should be executed before each .get(), .post(), etc
   })
  .afterEach(function(){
      // This should be executed after each .get(), .post(), etc
  })
   .tearDown(function(){
     // This should be executed after all the tests
   })
  .get('/').expect(....)
  .get('/').expect(....)
  .post('/').expect(....)
  .export(module);

It'd be good if it has a way to execute those hooks in synchronous way, because some times one need to populate the DB and has to wait until the driver executes the callback, if it were async then the tests will continue running while the DB is still being populated.

API-easy not compatible with latest versions of node?

Hi,
First of all, thanks for your work on API-easy, I use it everyday! :)
For one of my project I upgraded node from 0.8 to 0.10 but I discovered some issues.

I tried the code below with different version of node and the latest version of API-easy.

var assert = require('assert'),
    APIeasy = require('api-easy'),
    suite = APIeasy.describe('api');

suite.discuss('Test Resource /users')
    .use('localhost', 4001)
    .expect(401)
    .expect('Missing token', function (err, res, body) {
        assert.equal(body, 'Missing Cookie auth');
    })
    .export(module);

Node v0.8.9 / API-easy v0.3.7. Everything is fine.

./node-v0.8.9-linux-x64/bin/node user.vows.js 
·· ✓ OK » 2 honored (0.022s)

Node v0.9.9 / API-easy v0.3.7. No error message, but at least we know there is two errors.

./node-v0.9.9-linux-4/bin/node user.vows.js 
✗ Errored » 1 errored

✗ Errored » 1 errored

Node v0.10.4 / API-easy v0.3.7. No error message and only one error...

./node-v0.10.4-linux-x64/bin/node user.vows.js 
✗ Errored » 1 errored

Node v0.11.0 / API-easy v0.3.7. The same as previous.

./node-v0.11.0-linux-x64/bin/node user.vows.js 
✗ Errored » 1 errored

Does anyone have the same issue?
FYI, I am using Ubuntu 12.04.

.delete

There doesn't seem to be a DELETE method.

Supporting file upload

Hey,

What do you think of supporting file uploading? I've found api-easy to be super useful for testing my RESTful API, but to test uploads I've been falling back on scripted curl requests.

Support OAuth Endpoints

It would be great if APIEasy could take the hassle out of testing 2-legged & 3-legged OAuth resources (3-legged creating the biggest hassle.) Granted, there would have to be some DOM manipulation for entering u/p and submitting, but that would be easy to do with something like jsdom.

afters

Description

Just as there is a befores implementation it would be nice to have an afters function as I am trying to wrap the library in a promise and resolve it at the end of the function.

Code example

return new Promise(function(resolve, reject) {
    suite.discuss('When authenticating')
        .discuss('with no username or password')
        .after(resolve) // this part 
        .next();
});

.put() does not work as expected

This is a little test suite i use to test a simple API:

var APIeasy = require('api-easy');

var suite = APIeasy.describe('API');

suite.use('localhost', 3000)
    .path('/users')
    .get()
        .expect(200)
    .next()

    .put({ firstName: 'Testibus', lastName: 'Testman' })
        .expect(200)

    .export(module)

However, the .get() works as expected but the .put() results in an error:

    A PUT to /users
      ✗ should respond with 200
        » expected 200,
    got  undefined (==) // api-easy.js:290
  ✗ Broken » 1 honored ∙ 1 broken (0.304s)

Am i missing something?

How to use the same tests in different environments (dev, beta, staging etc.)

I'm using APIEasy via grunt and the grunt-vows plugin.

Is there some way I could set up the different host names for different server environments as grunt task targets, then use the config info in the tests?

Alternatively, could I set some variables in a set of wrapper scripts which then require or include the actual tests?

Trademark Infringement on RESTEasy

Please don't be too mad,

I couldn't find how to contact you privately so I had to ping you here.

Red Hat owns the trademark on RESTEasy based on our project of the same name that has existed for over 3+ years. Since you are starting your own business it seems at Nodejitsu, I hope that you can understand, as entrepreneurs, that Trademark is one of the few assets that an open source project has.

Please change the name.

Sincerely,

Bill Burke, founder Red Hat's RESTEasy project.

2 test with same method and uri

Just one test will be execute they have same method and uri.

example:

     .post('/tests', { dynamic: true })
       .expect(200, { dynamic: true })
     .post('/tests', { dynamic: false })
       .expect(200, { dynamic: false })

I think it's because of this line in api-easy.js:

      // Create the description for this test. This is currently static.
      // **Remark _(indexzero)_**: Do users care if these strings are configurable?
      //
      this.current = ['A', method.toUpperCase(), 'to', fullUri].join(' ');
      context = this._currentTest();

Asynchonous error

Hi,

I'm getting the following error

? Errored » Asynchronous Error
    in api smoke test for myurl.org
    in undefined
Errored » callback not fired
    in When doing a smoke test on myurl.org A GET to /login
    in api smoke test for myurl.org

I'm struggling to figure out if this is my fault or not. I'm running a suite more than once if it fails I retry it. Do you think this would cause an issue? Should I create a new suite for each run?

Recursive calls Examples

Can you provide an example of recursive calls. I'm interested in the case where you, let's say authenticate a call to an API and then use the token returned for future calls.

Thanks!

Better documentation

Really need some better documentation. I would like to see some wiki pages please with examples.

use without vows

If it's possible to use this inside a different, non-vows, test framework (eg nodeunit, expresso), could you please document that?

Bypass "deepEqual" for fuzzier expectations

Hi,
For certain assertions I'm making, I would rather so a looser equal on the result. I was wondering if there was a nice way of doing something like

 ....expect (200, { result { randomData: "*"}})

I basically want to assert on the structure of the response rather than the data/all of the data.

Thanks,
Chris

"Content-Type" header with GET request

Leaving the "Content-Type" header set for all requests (including GET which does not have any body) seems to confuse the Express.js router and a simple GET to /resource after a POST to /resource with 'Content-Type': 'application/json' returns 400.

Would it be right to remove the header for GET requests? If so I'll make a pull request.

Failed Assertion: Colors & Text Scrambled / Missing Spacing

Starting server in test mode
initializing routes
restify listing at http://0.0.0.0:8989
···✗✗✗✗  

    When posting an invalid worm is should 400 A POST to /worms/worms 
      ✗ should respond with 400 
        » expected 400, 
        got      404 (==) // api-easy.js:290 

      ✗ should have a proper 400 error body 
        »        
        actual expected 

        InvalidObjResourceNotFound 
         // server.coffee:33 

    When posting a valid worm it should save correctly A POST to /worms 
      ✗ should respond with 200 
        » expected 200, 
        got      404 (==) // api-easy.js:290 

      ✗ should have a proper post body 
        »        
        actual expected 

        SResourccessNotFound 
         // server.coffee:41 
  ✗ Broken » 3 honored ∙ 4 broken (0.030s) 

In the first instance the "InvalidObjResourceNotFound" I'm expecting a space between 'InvalidObj' and 'ResourceNotFound' also for some reason the first e, c, and t in 'ResourceNotFound' are black instead of Red.

In the second instance I have to assertions in the same callback and it looks like it's printing both failures on the same line and blending the characters, leading to gibberish and christmas colors (red, black and green all blended and crazy).

The tests producing strange output (written in coffeesript):

          .expect('should have a proper 400 error body', (err, res, body) ->
            body = JSON.parse body
            assert.equal body.code, 'InvalidObject'
          )
          .expect('should have a proper post body', (err, res, body) ->
            body = JSON.parse body
            assert.equal body.code, 'Success'
            assert.equal body.location, '/worms/1' #first worm posted
          )

Weird error - Callback not fired

Hi there,

Nice testing library!

When running the example provided in the README and running tests against my own API implementation I get a callback not fired error.

Errored » callback not fired

What callback is this referring to as I am not testing for any callbacks being fired?

Quick fix in .expect()

There is a small error in the assert.deepEqual when testing the JSON body. The arguments passed to assert.deepEqual should be swapped as result is the expected value and not testResult which is the actual value parsed from the body.

Cannot post a body that is not an object

I'm testing an existing XML-based rest API. The XML string is not sent with:
.post("/my/uri", "xml")

I looked into api-easy.js, and I think I see the issue. The _request function expects data to be an object. Fixing this would not be as simple allowing the body to be either an object or string -- as this could possibly conflict with the uri parameter.

One possible solution is to have the body continue to be an object, but for unkown mime types, encapsulate the data in an object, like such: {"body": "xml"}.

api-easy usage with Express (node lib)

Hi!

One month ago I used api-easy to test an api built on Express (node package). But this don't work anymore. Before, I was able to make a suite of calls that reuse the session made by the login. Now, each call after the login fails and I received 401 (unauthorized).

My express api (server) code manage sessions like this :

req.session.loggedIn = true;
req.session.accountId = account._id;

res.send(200);


Code example that fails now (on the account/authenticated call) :

suite.discuss('Login attempt with good credentials')
.post('/login', null, {'email':'[email protected]', 'password':'12345'})
.expect(200)
.next()
.discuss('Validate session after the login')
.get('/account/authenticated')
.expect(200)
.export(module);


I can't figure out what have change or how to test my code with api-test now. Should I update something?

Thanks!

Question: using for a smoke test

Hi,

I started using api-easy today for doing a quick smoke test of my application which was super simple to get working.

I run the smoke test after a fresh deployment and usually in the first test run some of the tests might fail with a timeout but pass thereafter.

I was wondering if it would be possible to build in a retry mechanic - I'd be happy to put in the effort and submit a PR but would need a nudge in the right direction as I'm quite new to node.

Thanks
Neil

npm install installs vows without the -g flag

Running npm install without or without sudo as per the Readme doesn't install vows with the -g flag. But the -g flag is required to install vows CLI to allow tests to be run with the command line you document in the api-easy Readme. After I separately did an npm install -g vows then vows was recognized from the command line.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.