How to implement unit testing within the strapi framework - node.js

I'm experimenting with Strapi and would like to create a controller verified by unit tests.
How do I setup Unit tests within Strapi?
I have written the following test
test('checks entity inside boundary',async ()=> {
ctx={};
var result = await controller.findnearby(ctx);
result = {};
expect(result).anyting();
});
however, inside my Controller I have code that accesses a global strapi object, which causes this error ReferenceError: strapi is not defined
strapi.log.info('findNearby');
strapi.log.info(ctx.request.query.lat);
strapi.log.info(ctx.request.query.long);
What is the best practice with Strapi and testing?

I've managed to achieve testing in Strapi by creating a helper
const Strapi = require("strapi");
// above require creates a global named `strapi` that is an instance of Strapi
let instance; // singleton
async function setupStrapi() {
if (!instance) {
await Strapi().load();
instance = strapi;
instance.app
.use(instance.router.routes()) // this code in copied from app/node_modules/strapi/lib/Strapi.js
.use(instance.router.allowedMethods());
}
return instance;
}
module.exports = { setupStrapi };
You can get all of the controllers from app.controllers now and test them one by one.
My example test (in Jest) for API endpoint would look like
const request = require("supertest");
const { setupStrapi } = require("./helpers/strapi");
// We're setting timeout because sometimes bootstrap can take 5-7 seconds (big apps)
jest.setTimeout(10000);
let app; // this is instance of the the strapi
beforeAll(async () => {
app = await setupStrapi(); // return singleton so it can be called many times
});
it("should respond with 200 on /heartbeat", (done) => {
request(app.server) // app server is and instance of Class: http.Server
.get("/heartbeat")
.expect(200) // expecting response header code to by 200 success
.expect("Hello World!") // expecting reponse text to be "Hello World!"
.end(done); // let jest know that test is finished
});
I've tried to cover this topic on my blog https://medium.com/qunabu-interactive/strapi-jest-testing-with-gitlab-ci-82ffe4c5715a

There were some changes, regarding the version upgrade.
I recommend to switch to stable 3.0.0 and use the following snippet
const Strapi = require("strapi");
const http = require("http");
let instance;
async function setupStrapi() {
if (!instance) {
/** the follwing code in copied from `./node_modules/strapi/lib/Strapi.js` */
await Strapi().load();
instance = strapi; // strapi is global now
await instance.app
.use(instance.router.routes()) // populate KOA routes
.use(instance.router.allowedMethods()); // populate KOA methods
instance.server = http.createServer(instance.app.callback());
}
return instance;
}
module.exports = { setupStrapi };
Here is my example project.
Guide for this is hopefully coming to Strapi documentation sooner then later.

Related

Calling a stored procedure with sequelize and an express API

This is probably pretty easy but I've been unable to piece it together properly.
I'm trying to use the Sequelize NPM to call a stored procedure that I built and then I want to trigger it with a GET request from from an express api and return the output of the procedure to the api.
Here is what my code looks like for the Sequelize portion....
// Testing stored procedure //
const Retrieve = (testName) => connection.testdata_connection.query("EXEC [SPROC] [INPUTS]")
module.exports = {
tests: Tests(),
retrieve: Retrieve()
};
This part "connection.testdata_connection" is just establishing my connection to the database and I have tested this and I know this part is set.
I would like to be able to hit that with something like...
const query = require('./database/queries'); ///Imports sequelize queries
const app = express();
app.get('/decrypt', function(req,res){
query.retrieve()
})
})
This doesn't work at all.
Now if I do something like this in the queries file...
const Retrieve = async function() {
const decrypt = await connection.testdata_connection.query("EXEC [SPROC] [INPUT]")
console.log(decrypt)
}
module.exports = {
tests: Tests(),
retrieve: Retrieve()
};
This will log to my console with correct data when I start the server. I want it to do that when I hit it with my endpoint.
First, your function should be exported but not executed:
// creating an async function (all i/o operations should be async).
const Retrieve = async(testName) => connection.testdata_connection.query("EXEC [SPROC] [INPUTS]")
module.exports = {
retrieve: Retrieve,
// retrieve: Retrieve() if you call the function with (), the function will be executed and we don't want that yet
};
Now we can call it in the route:
const query = require('./database/queries'); ///Imports sequelize queries
const app = express();
// the route is async because it is executing async code
app.get('/decrypt', async (req,res) => {
// waiting for the answer with await
const response = await query.retrieve();
// Doing something with the response
})
You still need to check for errors, but that is the basics.

Sinon stubs not being used

I have a lambda function which is referencing a module define in another file.
Instead of having to get all the scenarios right in my tests I'd like to just have a stub of that module.
In app.js
var Service = require("./Services/Service");
let service = new Service(event.Environment);
var regulatorResults = await service.GetModifiers(budgeted, irrelevant, variables);
Service is defined as follows:
function Service(environment) {
async function GetModifiers(ids, irrelevant, things) {
//logic happens
}
return { GetModifiers };
}
module.exports = Service;
I've tried many different iterations in my tests but where I feel closest is with this:
var assert = require('assert');
var sinon = require('sinon');
var service = require('../Services/Service');
describe('Calculator', function () {
before(function () {
var service = sinon.createStubInstance(regulatorService);
sinon.stub(service.prototype, "GetModifiers").callsFake(async function(IDs, thing, thing) {
return IDs.map(id=> new { ID: id, Modifier: 1 });
});
});
When I run the actual tests, the results of that service are [], even if I code the stub to return a set value, and if I throw in a console.log, it doesn't get hit ever.
The tests fail with error Error: Trying to stub property 'GetModifiers' of undefined.
Thanks for your help!

How to send data to local callable function? (Firebase CLI Shell) [duplicate]

I can't seem to find the solution for this in the Firebase Documentation.
I want to test my functions.https.onCall functions locally. Is it possible using the shell or somehow connect my client (firebase SDK enabled) to the local server?
I want to avoid having to deploy every time just to test a change to my onCall functions.
My code
Function :
exports.myFunction = functions.https.onCall((data, context) => {
// Do something
});
Client:
const message = { message: 'Hello.' };
firebase.functions().httpsCallable('myFunction')(message)
.then(result => {
// Do something //
})
.catch(error => {
// Error handler //
});
For locally you must call (after firebase.initializeApp)
firebase.functions().useFunctionsEmulator('http://localhost:5000')
Although the official Firebase Cloud Function docs have not yet been updated, you can now use firebase-functions-test with onCall functions.
You can see an example in their repository.
I have managed to test my TypeScript functions using jest, here is a brief example. There are some peculiarities here, like import order, so make sure to read the docs :-)
/* functions/src/test/index.test.js */
/* dependencies: Jest and jest-ts */
const admin = require("firebase-admin");
jest.mock("firebase-admin");
admin.initializeApp = jest.fn(); // stub the init (see docs)
const fft = require("firebase-functions-test")();
import * as funcs from "../index";
// myFunc is an https.onCall function
describe("test myFunc", () => {
// helper function so I can easily test different context/auth scenarios
const getContext = (uid = "test-uid", email_verified = true) => ({
auth: {
uid,
token: {
firebase: {
email_verified
}
}
}
});
const wrapped = fft.wrap(funcs.myFunc);
test("returns data on success", async () => {
const result = await wrapped(null, getContext());
expect(result).toBeTruthy();
});
test("throws when no Auth context", async () => {
await expect(wrapped(null, { auth: null })).rejects.toThrow(
"No authentication context."
);
});
});
There is a simple trick, how you can simplify onCall -function testing. Just declare the onCall function callback as a local function and test that instead:
export const _myFunction = (data, context) => { // <= call this on your unit tests
// Do something
}
exports.myFunction = functions.https.onCall(_myFunction);
Now you can variate all cases with a normal function with the input you define on your function call.
Callables are just HTTPS functions with a specific format. You can test just like a HTTPS function, except you have to write code to deliver it the protocol as defined in the documentation.
you should first check for dev environment and then point your functions to local emulator.
For JS:
//after firebase init
if (window.location.host.includes("localhost") ||
window.location.host.includes("127.0.0.1")
) {
firebase
.app()
.functions() //add location here also if you're mentioning location while invoking function()
.useFunctionsEmulator("http://localhost:5001");
}
or if you don't create instance of firebase then
//after firebase init
if (window.location.host.includes("localhost") ||
window.location.host.includes("127.0.0.1")
) {
firebase
.functions()
.useFunctionsEmulator("http://localhost:5001");
}
or when serving pages from backend (node.js):
//after firebase init
if (process.env.NODE_ENV === 'development') {
firebase.functions().useFunctionsEmulator('http://localhost:5001');
}
if you are using angularfire, add this to you app.module
{
provide: FirestoreSettingsToken,
useValue: environment.production
? undefined
: {
host: "localhost:5002",
ssl: false
}
}

mocha wait for db connection before running tests

Basically to run the tests, I need the database connection to MongoDb to be ready. The database manager has a connect method to get a connection and a get method to get a reference to that connection. The connect method is supposed to be called only once, at startup. This design works fine when running the app because the app starts only if the connect method has been called and returned a connection.
The database manager using mongo native (no mongoskin, no mongoose) (database.js):
var config = require('../config')
var MongoClient = require('mongodb').MongoClient
var connection = null
module.exports.connect = function () {
return MongoClient.connect(config.mongodb.uri + config.mongodb.db)
.then(function (db) {
connection = db
})
}
module.exports.get = function () {
if (!connection) {
throw new Error('Call connect first!')
}
return connection
}
Start the app:
var db = require('./services/database');
db.connect()
.then(function() {
logger.info("mongodb is running");
require('./main')
});
In the application, there are service modules and repository modules. The service modules require repository modules when they have to use the database. I have a BaseRepository module where I define the common queries findOne, findAll, etc. For all the MongoDb collections I have a specific repository that inherits from the BaseRepository. In the constructor, I set the collection by calling the database connection.
It means that when a service module which has a dependency on a repository module, the database connect method has to be called. This is the issue because I don't know how to start all the tests after the call to that connect method. I am open to suggestions if you think it's a design flow.
A test:
var usersService = require('../../services/rest/UsersService');
describe('IT Test', function () {
})
The service:
var usersRepository = require('../dao/UsersRepository');
The repository:
var db = require('../database');
var BaseRepository = require('./BaseRepository');
var util = require('util');
util.inherits(UsersRepository, BaseRepository);
function UsersRepository() {
this.collection = db.get().collection('users'); // <====== The connect method has not been called yet
}
The base repository:
function BaseRepository() {
this.collection = undefined;
}
BaseRepository.prototype.findOne = function (filters) {
return this.collection.findOne(filters)
}
I ended up writing a wrapper for all the tests
'use strict'
var logger = require('../services/logger')()
var db = require('../services/database')
describe('All tests wrapper', function () {
it('should pass', function () {
return db.connect()
.then(function () {
logger.info('MongoDB is running')
require('./agenda/AgendaJobsIT')
require('./dashboard/DashboardsServiceIT')
require('./twitter/TwitterServiceTest')
require('./authentication/TokenServiceTest')
})
})
})
The best way is mocha --delayed switch. mocha doc says
If you need to perform asynchronous operations before any of your suites are run (e.g., for dynamically generating tests), you may delay the root suite. Run mocha with the --delay flag.
For example, use mocha in this way mocha --recursive --exit --delay --ui tdd tests.js and --delayed enable you to trigger running the root suite via calling run() explicitly.
const fn = async x => {
return new Promise(resolve => {
setTimeout(resolve, 1500, 2 * x);
});
};
(async function() {
const z = await fn(3);
suite("won't run until run() executes", () => {})
run();
})();
For more information, please read https://mochajs.org/#delayed-root-suite.

Node.js Unit Testing - Create Mock Layer Using mocha

I have written a web service that indeed consumes an external web service, after performing some validations and data manipulations (plain business logic). I need to write unit tests for the same and need to have code coverage report as well. Many suggestions are available on the net, Mocha being the prominent one. But in all available examples, actual DB call or external service call happens. My requirement is not to have actual external service call, but just mock the call (Just how, we do using EasyMock in Java). Any help would be highly appreciated. Is it possible to create a mock of DB call or external service call in node js ?
You can use Sinon with Mocha to provide stubbing, or spying of calls. It is important to also write you code to aid mocking. For instance, don't assume the database handler exists in the web component, pass it in through the constructor. That way its easy for testing to replace the database with your own "mocked" version.
As an example - here is the start of one of my bunch of tests where I instanciate a mocked connection pool to my database handler and pass it into the test. I am actually also mocking the web server, and just testing the API.
(function(){
'use strict';
const DBHOST = 'dummy';
const DBNAME = 'dummy';
const expect = require('chai').expect;
const sinon = require('sinon');
const mock = require('../mock/server');
const DB = require('../database');
const Logging = require('../log');
Logging.debug(true); //set to log debug message
const logger = Logging.logger;
const poolPromise = mock.getPool();
const db = new DB(mock.Pool,DBNAME,logger);
const API = require('../api');
const api = new API(mock.server,db,logger);
describe('API tests',function() {
let sandbox, testConnection;
beforeEach(function(){
sandbox = sinon.sandbox.create();
testConnection = new mock.Connection();
return poolPromise.then(pool => {
pool.getConnection().then(cb => {
cb(null,testConnection);
});
});
});
afterEach(function(){
sandbox.restore();
});
describe('/logon',function() {
let params;
describe('Admin', function() {
beforeEach(function() {
params = {name: 'Admin', password: 'abcde'};
})
it('Performs logon if username is "Admin" and password is good', function(done) {
mock.server.emit('/logon', params, (status,user) => {
expect(status).to.be.true;
expect(user).to.be.an('object');
expect(user.uid).to.be.equal(0);
expect(user.name).to.be.equal('Super User');
expect(user.keys).to.be.equal('A');
done();
});
testConnection.next.then(value => {
let request = value.request;
expect(value.name).to.equal('execSql');
expect(request.sqlTextOrProcedure).to.equal('SELECT passwordsalt FROM Admin WHERE AdminID = 0');
request.emit('row', mock.makeRow('passwordsalt',['salt']));
request.callback(null,1);
return testConnection.next;
}).then(value => {
let request = value.request
expect(value.name).to.equal('callProcedure');
expect(request.sqlTextOrProcedure).to.equal('CheckPassword');
request.emit('row',mock.makeRow('a',[1]));
request.callback(null,0);
});
});

Resources