Testing Node with Mocha/Chai

Learning Objectives

  • Describe the importance of testing your code programmatically
  • Use describe and assertion functions to do basic testing

Framing

We've now created a number of applications. Many of these apps cover a single topic, so most of the time, they are quite small. But when you create a larger application, the codebase will become bigger and more complex every time you add some features. At some point, adding code in File A will break features in File B, and to avoid these "side-effects", or at least recognize to immediately when they happen, we need to write tests our app and run them on each change.

As programmers, we use code to solve problems. Most libraries and frameworks have testing libraries available that let us write code to evaluate the robustness, completeness and flexibility of our applications. In a production-level application, providing a high level of test coverage for an application is almost always required in order to guarantee that code is bug-free and functions as intended.

There are many types of tests that we can create for our applications:

  • Unit tests: the smallest, most microscopic level of testing. Evaluates individual methods and functions within a codebase. The kind we'll be writing today!
  • Integration tests: ensure that different services and modules work together.
  • End-to-end tests: verify that application responds as expected to user interactions, such as evaluating how user input edge cases are handled.
  • Performance tests: also known as load testing, and evaluate application's response to heavy traffic (number of requests, large amounts of data).
  • Acceptance tests: ensure that the application meets its given business requirements.

... and more! As we graduate from focusing on how to build applications, we focus on learning how to build better applications. The next level of sophistication we can introduce is testing coverage to ensure that our applications are robust and can maintain their integrity in the face of changes. Automated testing is also an important part of the Continuous Integration/Continuous Delivery model in DevOps. For many junior developers and engineers, their first few weeks or months at an organization might include writing tests to gain familiarity with a codebase.

TDD: Test-Driven Development

A development methodology of writing the tests first, then writing the code to make those tests pass. Thus the process is:

  1. Define a test set for the unit
  2. Implement the unit
  3. Verify that the implementation of the unit makes the tests succeed
  4. Refactor
  5. Repeat

Essential Questions

❓ How is this approach different than the one we've taken so far when building our APIs?

Intro to JavaScript Testing with Mocha & Chai

To test our code in Node, we will use two primary libraries: one to run the tests and a second one to run the assertions.

Mocha will be our testing framework, but we're mostly just using it as a test runner. From the Mocha Website...

"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 (associating) uncaught exceptions to the correct test cases."

For assertions, we will use Chai. From the Chai website...

"Chai is a BDD / TDD assertion library for Node and the browser that can be delightfully paired with any JavaScript testing framework."

Q: What the heck is an assertion? It's a way of writing a unit test that tests whether or not a test case is passing or failing by comparing the expected result with the actual result of a test.

To be able to make HTTP requests inside tests, we will use Supertest...

"The motivation with this module is to provide a high-level abstraction for testing HTTP"

What is an API? What is API Testing?

What is an API?

APIs are mechanisms that enable two software components to communicate with each other using a set of definitions and protocols. For example, the weather bureau’s software system contains daily weather data. The weather app on your phone “talks” to this system via APIs and shows you daily weather updates on your phone.

What does API stand for?

API stands for Application Programming Interface. In the context of APIs, the word Application refers to any software with a distinct function. Interface can be thought of as a contract of service between two applications. This contract defines how the two communicate with each other using requests and responses. Their API documentation contains information on how developers are to structure those requests and responses.

How do APIs work?

API architecture is usually explained in terms of client and server. The application sending the request is called the client, and the application sending the response is called the server. So in the weather example, the bureau’s weather database is the server, and the mobile app is the client.

There are four different ways that APIs can work depending on when and why they were created.

SOAP APIs

These APIs use Simple Object Access Protocol. Client and server exchange messages using XML. This is a less flexible API that was more popular in the past.

RPC APIs

These APIs are called Remote Procedure Calls. The client completes a function (or procedure) on the server, and the server sends the output back to the client.

Websocket APIs

Websocket API is another modern web API development that uses JSON objects to pass data. A WebSocket API supports two-way communication between client apps and the server. The server can send callback messages to connected clients, making it more efficient than REST API.

REST APIs

These are the most popular and flexible APIs found on the web today. The client sends requests to the server as data. The server uses this client input to start internal functions and returns output data back to the client. Let’s look at REST APIs in more detail below.

What are REST APIs?

REST stands for Representational State Transfer. REST defines a set of functions like GET, PUT, DELETE, etc. that clients can use to access server data. Clients and servers exchange data using HTTP.

The main feature of REST API is statelessness.

Statelessness means that servers do not save client data between requests. Client requests to the server are similar to URLs you type in your browser to visit a website. The response from the server is plain data, without the typical graphical rendering of a web page.

What is web API?

A Web API or Web Service API is an application processing interface between a web server and web browser. All web services are APIs but not all APIs are web services. REST API is a special type of Web API that uses the standard architectural style explained above.

The different terms around APIs, like Java API or service APIs, exist because historically, APIs were created before the world wide web. Modern web APIs are REST APIs and the terms can be used interchangeably.

What are API integrations?

API integrations are software components that automatically update data between clients and servers. Some examples of API integrations are when automatic data sync to the cloud from your phone image gallery, or the time and date automatically sync on your laptop when you travel to another time zone. Enterprises can also use them to efficiently automate many system functions.

What are the benefits of REST APIs?

REST APIs offer four main benefits:

  1. Integration
    APIs are used to integrate new applications with existing software systems. This increases development speed because each functionality doesn’t have to be written from scratch. You can use APIs to leverage existing code.
  2. Innovation
    Entire industries can change with the arrival of a new app. Businesses need to respond quicklyand support the rapid deployment of innovative services. They can do this by making changes atthe API level without having to re-write the whole code.
  3. Expansion
    APIs present a unique opportunity for businesses to meet their clients’ needs across differentplatforms. For example, maps API allows map information integration via websites, Android,iOS, etc. Any business can give similar access to their internal databases by using free or paidAPIs.
  4. Ease of maintenance
    The API acts as a gateway between two systems. Each system is obliged to make internalchanges so that the API is not impacted. This way, any future code changes by one party do notimpact the other party.

What are the different types of APIs?

APIs are classified both according to their architecture and scope of use. We have already explored the main types of API architectures so let’s take a look at the scope of use.

Private APIs

These are internal to an enterprise and only used for connecting systems and data within the business.

Public APIs

These are open to the public and may be used by anyone. There may or not be some authorization and cost associated with these types of APIs.

Partner APIs

These are only accessible by authorized external developers to aid business-to-business partnerships.

Composite APIs

These combine two or more different APIs to address complex system requirements or behaviors.

What is an API endpoint and why is it important?

API endpoints are the final touchpoints in the API communication system. These include server URLs, services, and other specific digital locations from where information is sent and received between systems. API endpoints are critical to enterprises for two main reasons:

  1. Security API endpoints make the system vulnerable to attack. API monitoring is crucial for preventing misuse.
  2. Performance API endpoints, especially high traffic ones, can cause bottlenecks and affect system performance.

How to secure a REST API?

All APIs must be secured through proper authentication and monitoring. The two main ways to secure REST APIs include:

  1. Authentication tokens
    These are used to authorize users to make the API call. Authentication tokens check that the users are who they claim to be and that they have access rights for that particular API call. For example, when you log in to your email server, your email client uses authentication tokens for secure access.
  2. API keys
    API keys verify the program or application making the API call. They identify the application and ensure it has the access rights required to make the particular API call. API keys are not as secure as tokens but they allow API monitoring in order to gather data on usage. You may have noticed a long string of characters and numbers in your browser URL when you visit different websites. This string is an API key the website uses to make internal API calls.

How to create an API?

Due diligence and effort are required to build an API that other developers will want to work with and trust. These are the five steps required for high-quality API design:

  1. Plan the API

    API specifications, like OpenAPI, provide the blueprint for your API design. It is better to think about different use cases in advance and ensure the API adheres to current API development standards.
  2. Build the API
    API designers prototype APIs using boilerplate code. Once the prototype is tested, developers can customize it to internal specifications.
  3. Document the API
    While APIs are self-explanatory, API documentation acts as a guide to improve usability. Well-documented APIs that offer a range of functions and use cases tend to be more popular in a service-oriented architecture.
  4. Test the API
    API testing is the same as software testing and must be done to prevent bugs and defects. API testing tools can be used to strength test the API against cyber attacks.

How to write API documentation?

Writing comprehensive API documentation is part of the API management process. API documentation can be auto-generated using tools or written manually. Some best practices include:

  • Writing explanations in simple, easy-to-read English. Documents generated by tools can become wordy and require editing.
  • Using code samples to explain functionality.
  • Maintaining the documentation so it is accurate and up-to-date.
  • Aiming the writing style at beginners
  • Covering all the problems the API can solve for the users.

What is API testing?

API testing strategies are similar to other software testing methodologies. The main focus is on validating server responses. API testing includes:

  • Making multiple requests to API endpoints for performance testing.
  • Writing unit tests for checking business logic and functional correctness.
  • Security testing by simulating system attacks.

Test Cases for API Testing

API test strategy

The test strategy is the high-level description of the test requirements from which a detailed test plan can later be derived, specifying individual test scenarios and test cases. Our first concern is functional testing — ensuring that the API functions correctly.

The main objectives in functional testing of the API are:

  • to ensure that the implementation is working correctly as expected — no bugs!
  • to ensure that the implementation is working as specified according to the requirements specification (which later on becomes our API documentation).
  • to prevent regressions between code merges and releases.

API as a contract — first, check the spec!

An API is essentially a contract between the client and the server or between two applications. Before any implementation test can begin, it is important to make sure that the contract is correct. That can be done first by inspecting the spec (or the service contract itself, for example a Swagger interface or OpenAPI reference) and making sure that endpoints are correctly named, that resources and their types correctly reflect the object model, that there is no missing functionality or duplicate functionality, and that relationships between resources are reflected in the API correctly.

The guidelines above are applicable to any API, but for simplicity, in this post, we assume the most widely used Web API architecture — REST over HTTP. If your API is designed as a truly RESTful API, it is important to check that the REST contract is a valid one, including all HTTP REST semantics, conventions, and principles (here, here, and here).

If this is a customer-facing public API, this might be your last chance to ensure that all contract requirements are met, because once the API is published and in use, any changes you make might break customers’ code.

(Sure, you can publish a new version of the API someday (e.g., /api/v2/), but even then backward compatibility might still be a requirement).

API Testing Approach

So, what aspects of the API should we test?

Now that we have validated the API contract, we are ready to think of what to test. Whether you’re thinking of test automation or manual testing, our functional test cases have the same test actions, are part of wider test scenario categories, and belong to three kinds of test flows.

API test actions

Each test is comprised of test actions. These are the individual actions a test needs to take per API test flow. For each API request, the test would need to take the following actions:

  1. Verify correct HTTP status code. For example, creating a resource should return 201 CREATED and unpermitted requests should return 403 FORBIDDEN, etc.
  2. Verify response payload. Check valid JSON body and correct field names, types, and values — including in error responses.
  3. Verify response headers. HTTP server headers have implications on both security and performance.
  4. Verify correct application state. This is optional and applies mainly to manual testing, or when a UI or another interface can be easily inspected.
  5. Verify basic performance sanity. If an operation was completed successfully but took an unreasonable amount of time, the test fails.

Test scenario categories

Our test cases fall into the following general test scenario groups:

  • Basic positive tests (happy paths)
  • Extended positive testing with optional parameters
  • Negative testing with valid input
  • Negative testing with invalid input
  • Destructive testing
  • Security, authorization, and permission tests (which are out of the scope of this post)

Happy path tests check basic functionality and the acceptance criteria of the API. We later extend positive tests to include optional parameters and extra functionality. The next group of tests is negative testing where we expect the application to gracefully handle problem scenarios with both valid user input (for example, trying to add an existing username) and invalid user input (trying to add a username which is null). Destructive testing is a deeper form of negative testing where we intentionally attempt to break the API to check its robustness (for example, sending a huge payload body in an attempt to overflow the system).

How to Test an API

Let’s distinguish between three kinds of test flows which comprise our test plan:

  1. Testing requests in isolation – Executing a single API request and checking the response accordingly. Such basic tests are the minimal building blocks we should start with, and there’s no reason to continue testing if these tests fail.
  2. Multi-step workflow with several requests – Testing a series of requests which are common user actions, since some requests can rely on other ones. For example, we execute a POST request that creates a resource and returns an auto-generated identifier in its response. We then use this identifier to check if this resource is present in the list of elements received by a GET request. Then we use a PATCH endpoint to update new data, and we again invoke a GET request to validate the new data. Finally, we DELETE that resource and use GET again to verify it no longer exists.
  3. Combined API and web UI tests – This is mostly relevant to manual testing, where we want to ensure data integrity and consistency between the UI and API.

We execute requests via the API and verify the actions through the web app UI and vice versa. The purpose of these integrity test flows is to ensure that although the resources are affected via different mechanisms the system still maintains expected integrity and consistent flow.

Best Practices of API Testing

Remember: a server's job is to respond to HTTP requests.

Every HTTP request consists of a request method and path. The path is the part of the URL following the domain. We likely have noticed paths when navigating the web.

Your browser always sends that request in a particular way that gives the server a hint as to the purpose of the request. This particular way is the method.

"GET" is one of these methods. It means the browser just wants to read (or "get") some information.

RESTful HTTP Methods

HTTP defines five main methods, each of which corresponds to one of the CRUD functionalities.

Method Crud functionality DB Action
GET read retrieve data
POST create add data
PUT update modify existing data
PATCH update modify existing data
DELETE delete delete existing data

Put vs Patch

So, wait -- there are 5 REST methods, but only 4 CRUD methods?

PUT and PATCH are both used for updating. Whenever you update your Facebook profile you're probably making a PUT or PATCH request. The difference is PUT would be intended to completely replace your profile, whereas PATCH would be intended to just change a few fields of your profile.

To clarify further, PATCH is replacing part of the data and PUT is replacing the whole thing.

What's the difference at a technical level between a GET and a POST request?

There is of course the difference in the METHOD type, but also in the request payload. A POST request for instance will contain all of the data necessary for creating some new object.

GET is for when you want to read something. The parameters of the GET request are used for identifying which piece of data the client would like to read. The parameters of the POST request are used for defining a new piece of data.

RESTful Routes

A route is a method plus a path...

Method + Path = Route

Each route results in an action.

REST can be translated in to RESTful Routes (routes that follow REST):

Action Method Path Action
index GET /engineers Read information about all engineers
create POST /engineers Create a new engineer
show GET /engineers/1 Read information about the engineer whose ID is 1
update PUT /engineers/1 Update the existing engineer whose ID is 1 with all new content
update PATCH /engineers/1 Update the existing engineer whose ID is 1 with partially new content
destroy DELETE /engineers/1 Delete the existing engineer whose ID is 1

Note that the path doesn't contain any of the words describing the CRUD functionality that will be executed. That's the method's job.

These routes are important to keep in mind as we build out our controllers. For a resource with full CRUD, the controller for that resource will likely have each of the above 7 routes.

Types of Bugs that API testing detects

Functional Testing

Testing Category Action Category Action Description
1. Positive Test Category
Execute Api with valid required paramaters
Validate Status Code All requests should return 2XX HTTP response depending on spec
Validate Payload Response should be valid JSON response and the response matches the data model
Validate State of Data/System GET Requests should not change any data,

POST, DELETE, PUT, PATCH should modify system as expected, this can be tested by making get requested and verifying data has been Created, Updated or Destroyed
Validate headers Verify that HTTP Headers have valid information, verify nothing is leaked in the headers that doesn't belong
Performance Sanity Check Verify that data is receieved in a timely manner
2. Positive + optional parameters
Execute API call with valid required parameters AND valid optional parameters
Run same tests as in Group 1, this time including the endpoint’s optional parameters
(e.g., filter, sort, limit, skip, etc.)
Validate Status Code As in group 1
Validate Payload As in group 1
In addition to group 1 testing check optional paramaters
filter: ensure the response is filtered on the specified value.
sort: specify field on which to sort, test ascending and descending options. Ensure the response is sorted according to selected field and sort direction.
skip: ensure the specified number of results from the start of the dataset is skipped
limit: ensure dataset size is bounded by specified limit.
limit + skip: Test pagination
Validate State As in group 1
Validate Headers As in group 1
Performance Sanity As in group 1
3. Negative Testing - valid input
Execute API calls with valid input that attempts illegal operations i.e:

Attempting to create a resource with a name that already exists (e.g., user configuration with the same name)
Attempting to delete a resource that doesn’t exist (e.g., user configuration with no such ID)
Attempting to update a resource with illegal valid data (e.g., rename a configuration to an existing name)
Attempting illegal operation (e.g., delete a user configuration without permission.)
And so forth.
Validate Status Code: 1. Verify that an erroneous HTTP status code is sent (NOT 2XX)
2. Verify that the HTTP status code is in accordance with error case as defined in spec
Validate payload: 1. Verify that error response is received
2. Verify that error format is according to spec. e.g., error is a valid JSON object or a plain string (as defined in spec)
3. Verify that there is a clear, descriptive error message/description field
4. Verify error description is correct for this error case and in accordance with spec
Validate Headers As in group 1
Performance Sanity Make sure errors are received in a timely and safe way and do not cause whole system to shut down
4. Negative testing – invalid input
Execute API calls with invalid input, e.g.:
Missing or invalid authorization token
Missing required parameters
Invalid value for endpoint parameters, e.g.:
Invalid UUID in path or query parameters
Payload with invalid model (violates schema)
Payload with incomplete model (missing fields or required nested entities)
Invalid values in nested entity fields
Invalid values in HTTP headers
Unsupported methods for endpoints
And so on.
Validate Status Code As in group 1
Validate State As in group 1
Validate Headers As in group 1
Performance Sanity As in group 1
5. Destructive Testing
Intentionally attempt to fail the API to check its robustness:
Malformed content in request
Wrong content-type in payload
Content with wrong structure
Overflow parameter values. E.g.:
Attempt to create a user configuration with a title longer than 200 characters
Attempt to GET a user with invalid UUID which is 1000 characters long
Overflow payload – huge JSON in request body
Boundary value testing
Empty payloads
Empty sub-objects in payload
Illegal characters in parameters or payload
Using incorrect HTTP headers (e.g. Content-Type)
Small concurrency tests – concurrent API calls that write to the same resources (DELETE + PATCH, etc.)
Other exploratory testing
Validate Status Code As in #3. API should fail gracefully.
Validate State As in #3. API should fail gracefully.
Validate Headers As in #3. API should fail gracefully.
Performance Sanity As in #3. API should fail gracefully.

Going Beyond Functional Testing

Following the test matrix above should generate enough test cases to keep us busy for a while and provide good functional coverage of the API. Passing all functional tests implies a good level of maturity for an API, but it is not enough to ensure high quality and reliability of the API.

In the next post in this series we will cover the following non-functional test approaches which are essential for API quality:

Security and Authorization

  • Check that the API is designed according to correct security principles: deny-by-default, fail securely, least privilege principle, reject all illegal inputs, etc.
  • Positive: ensure API responds to correct authorization via all agreed auth methods – Bearer token, cookies, digest, etc. – as defined in spec
  • Negative: ensure API refuses all unauthorized calls
  • Role Permissions: ensure that specific endpoints are exposed to user based on role. API should refuse calls to endpoints which are not permitted for user’s role
  • Protocol: check HTTP/HTTPS according to spec
  • Data leaks: ensure that internal data representations that are desired to stay internal do not leak outside to the public API in the response payloads
  • Rate limiting, throttling, and access control policies

Performance

  • Check API response time, latency, TTFB/TTLB in various scenarios (in isolation and under load)

Load Tests (positive), Stress Tests (negative)

  • Find capacity limit points and ensure the system performs as expected under load, and fails gracefully under stress

Usability Tests

  • For public APIs: a manual “Product”-level test going through the entire developer journey from documentation, login, authentication, code examples, etc. to ensure the usability of the API for users without prior knowledge of our system.

We Do: Create Tests

Setting up the app

Clone down the starter code from this repository. Take a moment to familiarize yourself with the Express app and get everything set up.

  1. Run npm install from the root directory.
  2. To test this app, we need to install a couple of dependencies. Let's install mocha, chai and supertest:
$ npm i mocha chai supertest --save-dev

Note that we are installing these as dev dependencies because they will be used to test our application but are not going to be used in production.

Run nodemon index.js to start your express server. The app will be served at localhost:3000. Check it out in the browser!

Files and Folders

Now that we're configured, let's set up our file and folder structure. All the tests will be written inside a folder test.

mkdir test

Then we will write the tests inside a file called gifs.test.js...

 $ touch test/gifs.test.js

Writing Our First Test

Open the file gifs.test.js. We now need to require some dependencies at the top of this file:

const should = require('chai').should()
const expect = require('chai').expect
const request = require('supertest')
const app = require('../index')

For more information on the difference between should and expect, let's turn to the very readable Chai documentation.

All the tests need to be inside a describe function. We will use one describe block per route:

describe("GET /gifs", () => {
  //tests will be written inside this function
})

First, we will write a test to make sure that a request to the index path /gifs returns a http status 200...

describe('GET /gifs', function() {
  it('responds with json', function(done) {
    request(app)
      .get('/gifs')
      .set('Accept', 'application/json')
      .expect('Content-Type', /json/)
      .expect(200, done);
  });
});

Let's break down what's happening here.

  • describe() is a function that takes 2 arguments

    • a string describing a group of operations that are about to happen (like the title of this group of tests)
    • a callback function that contains all of the individual tests
  • it() is a function that takes 2 arguments

    • a string describing some behavior e.g. "should return an array of objects"
    • a callback function that runs the actual test code. The callback takes an argument that you must call when the test is finished: e.g. done
  • Inside of it(), we are using an instance of supertest which we've assigned to the variable api.

    • The first method called on api is .get() which actually performs a get request to the specified URL
    • .set() sets an http header on the request. In this case we're specifying what type of data we want to receive
    • .expect() tests the response. In this case we're checking to see if the status code is 200. The second argument is the done function we've declared at the top of it(). Passing it in here tells the code we're finished with this block.

Now go in the command line and type mocha. When you do, you may get an error saying that the mocha command cannot be found.

This is because mocha is not installed globally on our machines (though it's possible you may have it already installed). While we could simply install mocha globally and run the test, we would not be using the specified version of mocha listed a dev dependency in our package.json and contained in node_modules.

In order to run mocha from our local node_modules folder, do the following:

  1. Run mocha directly from our node_modules folder to ensure you've installed it properly:

    node_modules/.bin/mocha --exit
  2. Alias the mocha command to an npm script in our package.json:

    {
      ...
      "scripts": {
        "test": "node_modules/.bin/mocha --exit"
      },
      ...
    }

    One thing to keep in mind when using NPM to run tests (really running anything with NPM scripts for that matter) is that NPM will prefer local node modules over globally installed modules. If something has not been installed properly locally this could lead to differing behavior between running mocha and npm test.

  3. Run npm test. Now we can run it locally from our projects without having to install it globally on our machines, and manage another globally installed package.
You can also simply run `npx mocha`, which will look inside the specified dependency and execute the binary it finds. `npx` executes whatever command you put after it, first looking in your `node_modules` directory. If it doesn't exist in `node_modules` then it is installed locally.

So you can also run npx mocha --exit and it will execute node_modules/.bin/mocha --exit for you.

You will know the test successfully ran when if get an output looking like this...

Imgur

This test is passing!

If you get an error like ECONNREFUSED make sure your express server is running.

If you get an error that says your server responded with a 404 instead of a 200 or if your test code hangs, try changing your PORT number to another one, like 3005!

Test Blocks

Every block of code that starts with it() represents a test. Each test is performed in sequence, one after the other, in a queue.

The callback represents a function that Mocha will pass to the code so that the next test will be executed only when the current is finished and the done function is called - this allows tests to be executed once at a time. It's almost like the resolve function of a Promise.

Now, let's verify the content of the response by looking at the data sent back by hitting the /gifs endpoint...

[
	{
		"name": "Kim Kardashian Emoji Gif",
		"url": "https://media.giphy.com/media/jyllN0iwpqydi/giphy.gif",
		"tags": ["phone", "emoji", "kim kardashian"]
	},
	{
		"name": "Help Me Cat",
		"url": "https://media.giphy.com/media/phJ6eMRFYI6CQ/giphy.gif",
		"tags": ["cat", "kitten", "help", "rescue", "help me"]
	},
	{
		"name": "Mean Girls Gif",
		"url": "https://media.giphy.com/media/iDcDa0KQD8Gpq/giphy.gif",
		"tags": ["mean girls", "wednesdays", "karen", "pink"]
	}
]

We can write a test that verifies the response is an array...

it("should return an array", done => {
  request(app)
    .get("/gifs")
    .set("Accept", "application/json")
    .end((error, response) => {
      expect(response.body).to.be.an('array');
      done()
    })
  })

NB: In the first test, we were using the .expect method of supertest. Here we are using the expect function provided by chai.

We can write another test that verifies the presence of a field in the response...

it("should return an array of objects that have a field called 'name' ", done => {
  request(app)
    .get("/gifs")
    .set("Accept", "application/json")
    .end((error, response) => {
        response.body.forEach(gif => {
          expect(gif).to.have.property('name');
        });
      done()
   })
})

We can also send data to the server and test the behavior - in our case, we want to make sure that when we post some JSON to /gifs, a new object is added to the array gifs.

Because we are going to test another route, lets add another describe block...

describe("POST /gifs", () => {

})

For this test, we need to:

  1. Create a new object by sending a POST request
  2. Verify that a new object has been "saved" by requesting the index route

For this, we will use before blocks. A before block will be executed ONCE BEFORE all the tests in the describe block.

Add this inside the new describe block...

describe("POST /gifs", () => {
  const newGif = {
		"name": "Beyonce Lemonade Gif",
		"url": "https://media.giphy.com/media/3o6ozBUuLfzTCngAFi/giphy.gif",
		"tags": ["Beyonce", "Bey"]
	};
  before(done => {
    request(app)
      .post('/gifs')
      .set('Accept', 'application/json')
      .send(newGif)
      .end(done);
  });
})

This code will be called at the beginning of the test block. There's also another method called beforeEach() which runs before every test.

For more information on the difference between before and beforeEach: https://stackoverflow.com/questions/21418580/what-is-the-difference-between-before-and-beforeeach

Now, we can verify that calling "POST" will add an object to gifs...

  it('should add a gif object to the collection gifs and return it', done => {
    request(app)
      .get('/gifs')
      .set('Accept', 'application/json')
      .end((error, response) => {
        expect(response.body.find(gif => gif.name === newCandy.name)).to.be.an(
          'object'
        );
        done();
      });
  });

Run npm test in your CLI, you should now have four passing tests!

❓❓ What other ways can we test whether this route works? How many times can you run these tests and have them pass?

Break (10 min / 1:15)

Practice

Write your own tests now!

  1. Write a test that makes sure the object is returned with right fields (i.e., id, name, color) when you call GET /gifs/:id.
  2. Write a test that ensures an object is deleted from the array gifs when you call DELETE /gifs/:id.
  3. Write a test that ensures a property is updated when you call PUT /gifs/:id.

Conclusion

We've covered the principles of testing in JavaScript, but Chai offers a lot of different expectations syntaxes. Check the Chai Documentation

  • How does Mocha work with Chai to write tests in your JavaScript application?
  • Describe how to configure your app to use Mocha and Chai.

How to do API Test Automation

What is Automated Testing?

Tests are automated by creating test suites that can run again and again. Postman can be used to automate many types of tests including unit tests, functional tests, integration tests, end-to-end tests, regression tests, mock tests, etc. Automated testing prevents human error and streamlines testing

Streamline Development and QA with a CI/CD Pipeline.

Testing APIs can be hard. Automating testing with your CI/CD Pipeline is easy. Postman allows you to reuse your test suites to create a CI/CD pipeline so you can test at every push. You can seamlessly integrate your Postman instance with Jenkins to create your own CI/CD pipeline or add it to your existing pipeline as a build step.

Make Scaling Easy with Automated Testing.

As programs grow, so does the risk of breakage. You can create more robust and bug-resistant programs by increasing test coverage and frequency. Postman and Newman, our command line tool, allow you to easily set up your own automated tests.

Sometimes Less is More.

Shifting to automated API tests means you'll spend less money on QA, experience less lag time between development and QA, and spend less time debugging in production.

Postman Simplifies Automated Testing.

Postman offers a comprehensive API testing tool that makes it easy to set up automated tests. You can aggregate the tests and requests you've created into a single automated test sequence. Run and manage your test workflow from the Postman app, Postman monitoring, or from the command line with Newman, Postman's command line tool.

What is Postman?

Postman is an application used for API testing. It is an HTTP client that tests HTTP requests, utilizing a graphical user interface, through which we obtain different types of responses that need to be subsequently validated.

Postman offers many endpoint interaction methods. The following are some of the most used, including their functions:

  • GET: Obtain information
  • POST: Add information
  • PUT: Replace information
  • PATCH: Update certain information
  • DELETE: Delete information

When testing APIs with Postman, we usually obtain different response codes. Some of the most common include:

  • 100 Series > Temporal responses, for example, ‘102 Processing’.
  • 200 Series > Responses where the client accepts the request and the server processes it successfully, for instance, ‘200 Ok’.
  • 300 Series > Responses related to URL redirection, for example, ‘301 Moved Permanently.’
  • 400 Series > Client error responses, for instance, ‘400 Bad Request’.
  • 500 Series > Server error responses, for example, ‘500 Internal Server Error.’

Collections

Postman gives the possibility to group different requests. This feature is known as ‘collections’ and helps organize tests.

These collections are folders where requests are stored and can be structured in whichever way the team prefers. It is also possible to export-import them.

Environments

Postman also allows us to create different environments through the generation/use of variables; for example, a URL variable that is aimed towards different test environments (dev-QA), enabling us to execute tests in different environments using existing requests.

Setup

Download Postman here if you don't have it already. You'll need to create an account (free!) to use it.

Postman is going to substitute for a front end for our testing purposes.

Writing Tests in Postman - with Examples | The Exploratory Video

Setup Instructions From Postman

Copyright © Per Scholas 2023