Trushar Narodia
Trushar Narodia

Reputation: 3873

Node js express js API test case with fake request and response data

I am working on write node js express js API test case using jasmine but I have one problem with my test case it's only working with live database connection and its return actual value from the database so in future may be data will change so at that time test case will fail.

So I want to create a mock of my controller and database query model

Here is my controller code

module.exports.updateProductStatus = async (req, res, next) => {
    let result = await apiModel.updateProductStatus(req.body);
    if (result.affectedRows > 0) {
        res.send({
            status: "success",
            message: "product status updated."
        })
    }
    else {
        res.send({
            status: "error",
            message: "failed to update product status."
        })
    }
};

and here is my MySQL database query module

module.exports.updateProductStatus = async (data) => {
    let updateData = {
        STATUS : data.status,
        UPDATED_TIMESTAMP : moment().unix()
    }
    let result = await db.query('UPDATE products SET ? WHERE PRODUCT_ID = ?',[updateData,data.product_id]).catch(handleError);
    return result[0];
};

Here is my test case to call API and send request data

it("should update product status", () => {
            var reqData = {
                "account_name" : "demo",
                "last_update": 1571298222,
                "product_detail": {
                    "QUENTITY":"36","PRICE":"5.69527","TITLE":"AAAAAAAAA","SKU":"A10512","UPC":""
                },
                "sku": "A10512AAA",
                "status": "inactive",
                "product_id": 1
            };
            request.post(
                {
                    url: 'localhost:9090/api/update-product-status',
                    headers: {
                        'authorization': 'Bearer eyJhbGciOiJIUzI1NiddIsInR5cCIdd'
                    },
                    form: reqData
                }, function (err, httpResponse, body) {
                    console.log("TCL: err", err);
                    console.log("TCL: body", body);
                    console.log("TCL: httpResponse", httpResponse);
                });
        });

please guide me how to get a response without connecting MySQL database when run test case, if there is an NPM library for mock, let me know event better if you provide sample code

thanks

Upvotes: 1

Views: 1511

Answers (1)

Lin Du
Lin Du

Reputation: 102207

It seems you are doing the integration tests which means your tests need to depend on the real database and other external services and resources.

Before we are doing the integration tests, we can write unit tests. Here is the unit tests based on your code:

folder structure:

.
├── api.js
├── api.spec.js
├── controller.js
├── controller.spec.js
└── db.js

api.js:

const moment = require('moment');
const db = require('./db');

module.exports.updateProductStatus = async data => {
  let updateData = {
    STATUS: data.status,
    UPDATED_TIMESTAMP: moment().unix()
  };
  let result = await db
    .query('UPDATE products SET ? WHERE PRODUCT_ID = ?', [updateData, data.product_id])
    .catch(handleError);
  return result[0];
};

function handleError(error) {
  console.error(error);
}

db.js:

module.exports = {
  async query() {
    return 'real query';
  }
};

controller.js:

const apiModel = require('./api');

module.exports.updateProductStatus = async (req, res, next) => {
  let result = await apiModel.updateProductStatus(req.body);
  if (result.affectedRows > 0) {
    res.send({
      status: 'success',
      message: 'product status updated.'
    });
  } else {
    res.send({
      status: 'error',
      message: 'failed to update product status.'
    });
  }
};

Unit tests:

controller.spec.js:

const controller = require('./controller');
const apiModel = require('./api');

describe('updateProductStatus controller', () => {
  const mReq = { body: {} };
  const mRes = { send: jest.fn() };
  beforeEach(() => {
    jest.restoreAllMocks();
  });
  test('should send success response', async () => {
    const mResult = { affectedRows: 2 };
    const updateProductStatusSpy = jest.spyOn(apiModel, 'updateProductStatus').mockResolvedValueOnce(mResult);
    await controller.updateProductStatus(mReq, mRes);
    expect(mRes.send).toBeCalledWith({ status: 'success', message: 'product status updated.' });
    expect(updateProductStatusSpy).toBeCalledWith(mReq.body);
  });

  test('should send error response', async () => {
    const mResult = { affectedRows: 0 };
    const updateProductStatusSpy = jest.spyOn(apiModel, 'updateProductStatus').mockResolvedValueOnce(mResult);
    await controller.updateProductStatus(mReq, mRes);
    expect(mRes.send).toBeCalledWith({ status: 'error', message: 'failed to update product status.' });
    expect(updateProductStatusSpy).toBeCalledWith(mReq.body);
  });
});

api.spec.js:

const apiModel = require('./api');
const db = require('./db');

describe('api', () => {
  beforeEach(() => {
    jest.restoreAllMocks();
  });
  test('should update product status', async () => {
    const mData = { product_id: 1, status: 'no store' };
    const mResult = [{ affectedRows: 1 }];
    const querySpy = jest.spyOn(db, 'query').mockResolvedValueOnce(mResult);
    const actualValue = await apiModel.updateProductStatus(mData);
    expect(actualValue).toEqual(mResult[0]);
    expect(querySpy).toBeCalledWith('UPDATE products SET ? WHERE PRODUCT_ID = ?', [
      { STATUS: 'no store', UPDATED_TIMESTAMP: expect.any(Number) },
      1
    ]);
  });

  test('should handle error', async () => {
    const mData = { product_id: 1, status: 'no store' };
    const mError = new Error('connection error');
    const querySpy = jest.spyOn(db, 'query').mockRejectedValueOnce(mError);
    const errorSpy = jest.spyOn(console, 'error');
    await expect(apiModel.updateProductStatus(mData)).rejects.toThrowError(
      new TypeError("Cannot read property '0' of undefined")
    );
    expect(querySpy).toBeCalledWith('UPDATE products SET ? WHERE PRODUCT_ID = ?', [
      { STATUS: 'no store', UPDATED_TIMESTAMP: expect.any(Number) },
      1
    ]);
    expect(errorSpy).toBeCalledWith(mError);
  });
});

Unit test result for controller.spec.js:

 PASS  src/stackoverflow/58692161/controller.spec.js
  updateProductStatus controller
    ✓ should send success response (10ms)
    ✓ should send error response (2ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.792s, estimated 3s

Unit test result for api.spec.js:

 PASS  src/stackoverflow/58692161/api.spec.js
  api
    ✓ should update product status (14ms)
    ✓ should handle error (13ms)

  console.error node_modules/jest-mock/build/index.js:860
    Error: connection error
        at /Users/elsa/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/58692161/api.spec.js:22:20
        at step (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/58692161/api.spec.js:33:23)
        at Object.next (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/58692161/api.spec.js:14:53)
        at /Users/elsa/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/58692161/api.spec.js:8:71
        at new Promise (<anonymous>)
        at Object.<anonymous>.__awaiter (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/58692161/api.spec.js:4:12)
        at Object.<anonymous> (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/58692161/api.spec.js:20:31)
        at Object.asyncJestTest (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:102:37)
        at resolve (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
        at new Promise (<anonymous>)
        at mapper (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
        at promise.then (/Users/elsa/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
        at <anonymous>
        at process._tickCallback (internal/process/next_tick.js:188:7)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        4.506s

Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58692161

Upvotes: 3

Related Questions