I am new to Jest and unit testing, I have an express API deployed on serverless(Lambda) on AWS.Express api uses dynamodb for crud operations
Note:- my api is based out of express and not just plain node, because on jest website they are telling ways for plain nodejs
I am able to do unit test on express on the methods which doesnt use dynamodb.However it fails for the methods which are using dynamodb, as to my understanding this has something to do with dynamodb being remote, because the code present in app.js corresponds to dyanmo db which is hosted on aws using lambda.
How do I go about it?
Note:- my api is based out of express and not just plain node
const isUrl = require('is-url');
const AWS = require('aws-sdk');
const { nanoid } = require('nanoid/async');
const express = require('express');
const router = express.Router();
const dynamoDb = new AWS.DynamoDB.DocumentClient();
// URL from users
router.post('/', async (req, res, next) => {
// urlId contains converted short url characters generated by nanoid
const urlId = await nanoid(8);
const { longUrl } = req.body;
// Veryfying url Format using isUrl, this return a boolean
const checkUrl = isUrl(longUrl);
if (checkUrl === false) {
res.status(400).json({ error: 'Invalid URL, please try again!!!' });
}
const originalUrl = longUrl;
const userType = 'anonymous'; // user type for anonymous users
const tableName = 'xxxxxxxxxxxxx'; // table name for storing url's
const anonymousUrlCheckParams = {
TableName: tableName,
Key: {
userId: userType,
originalUrl,
},
};
dynamoDb.get(anonymousUrlCheckParams, (err, data) => {
const paramsForTransaction = {
TransactItems: [
{
Put: {
TableName: tableName,
Item: {
userId: userType,
originalUrl,
convertedUrl: `https://xxxxxxxxxxxxxxxx/${urlId}`,
},
},
},
{
Put: {
TableName: tableName,
Item: {
userId: urlId,
originalUrl,
},
ConditionExpression: 'attribute_not_exists(userId)',
},
},
],
};
if (err) {
console.log(err);
res
.status(500)
.json({ error: 'Unknown Server Error, Please Trimify Again!' });
} else if (Object.keys(data).length === 0 && data.constructor === Object) {
dynamoDb.transactWrite(paramsForTransaction, async (error) => {
if (error) {
// err means converted value as userId is repeated twice.
console.log(error);
res
.status(500)
.json({ error: 'Unknown Server Error, Please trimify again. ' });
} else {
res.status(201).json({
convertedUrl: `https://xxxxxxxxxxxx/${urlId}`,
});
}
});
} else {
res.status(201).json({
convertedUrl: data.Item.convertedUrl,
});
}
});
});
module.exports = router;
my test.js
const request = require('supertest');
const app = require('../app');
test('Should convert url from anonymous user ', async () => {
await request(app)
.post('/anon-ops/convert')
.send({
longUrl: 'https://google.com',
})
.expect(201);
});
First off, if you're wanting to do unit testing. It doesn't really matter much if you're using express js or not, hence, the examples and information on the jest website are very valid to get you on your way.
How easy it is to do unit testing, mostly depends on how you have structured your code. For example, you could keep all your express js specific code in separate files and then only instantiate the files holding your actual business logic (which some might call a services layer) during your unit tests. That's at least one way where you could make it easier on yourself. Using a functional approach also makes your code easier to test or at the very least using dependency injection, so you can swap out dependencies during testing in order to test some functionality in isolation.
When it comes to DynamoDB, you've got two options. Either mocking or running a local version.
You can either mock the specific functions you're calling either using the jest mocks or using a mocking library such as sinon. Whichever you choose is mostly personal preference.
The second option is running a local version of DynamoDB in a docker container. This has the upside of also verifying your actual calls to the DynamoDB service (which you could do by verifying the mocks, but it's easy to make a mistake in the verification), however, it is more cumbersome to set up and your tests will be slower, so this might skew your test to be more integration tests than unit tests (but that distinction is an evening worth or arguing in itself).
If you want to go towards end-to-end testing of the entire API, you can have a look at the SuperTest NPM package.
(Edit) Added small example using sinon
const AWS = require('aws-sdk');
const sinon = require('sinon');
const ddb = new AWS.DynamoDB.DocumentClient();
const getStub = sinon.stub(AWS.DynamoDB.DocumentClient.prototype, "get");
getStub.callsFake((params, cb) => {
cb(null, {result: []});
});
ddb.get({foo: 'bar'}, (err, val) => {
console.log(val); // => { "result": [] }
})
I am trying to write unit testing for fastify application which also has custom fastify plugin.
Is there a way we can mock fastify plugin? I tried mocking using Jest and Sinon without much success.
Giorgios link to the file is broken, the mocks folder is now absent from the master branch. I dug the commit history to something around the time of his answer and I found a commit with the folder still there. I leave it here for those who will come in the future!
This is what works for me
Setup your plugin according to Fastify docs https://www.fastify.io/docs/latest/Reference/Plugins/
// establishDbConnection.ts
import fp from 'fastify-plugin';
import {FastifyInstance, FastifyPluginAsync} from 'fastify';
import { initDbConnection } from './myDbImpl';
const establishDbConnection: FastifyPluginAsync = async (fastify: FastifyInstance, opts) => {
fastify.addHook('onReady', async () => {
await initDbConnection()
});
};
export default fp(establishDbConnection);
mock the plugin with jest, make sure you wrap the mock function in fp() so that Fastify recognizes it as a plugin.
// myTest.ts
import fp from 'fastify-plugin';
const mockPlugin = fp(async () => jest.fn());
jest.mock('../../../fastifyPlugin/establishDbConnection', (() => {
return mockPlugin;
}));
Your question is a bit generic but if you are using Jest it must be enough for mocking a fastify plugin. You can take a look in this repo and more specifically this file . This is a mock file of fastify and you add the registered plugins and in the specific example addCustomHealthCheck and then in your test files you can just call jest.mock('fastify').
You do not give a specific use case and there are lot of reasons you might want to mock a plugin. The nature of the plugin to be mocked is important to giving a good answer. Because I don't know that specific information I will show how to mock a plugin that creates a decorator that stores data that can be retrieved with fastify.decorator-name. This is a common use case for plugins that connect to databases or store other widely needed variables.
In the below case, the goal is to test a query function that queries a db; a plugin stores the connection information via a fastify decorator. So, in order to unit test the query we specifically need to mock the client data for the connection.
First create an instance of fastify. Next, set up a mock to return the desired fake response. Then, instead of registering the component with fastify (which you could also do), simply decorate the required variables directly with mock information.
Here is the function to be tested. We need to mock a plugin for a database which creates a fastify decorator called db. Specifically, in the below case the function to be tested uses db.client:
const fastify = require("fastify")({ //this is here to gather logs
logger: {
level: "debug",
file: "./logs/combined.log"
}
});
const HOURS_FROM_LOADDATE = "12";
const allDataQuery = `
SELECT *
FROM todo_items
WHERE a."LOAD_DATE" > current_date - interval $1 hour
`;
const queryAll = async (db) => {
return await sendQuery(db, allDataQuery, [HOURS_FROM_LOADDATE]);
};
//send query to db and receive data
const sendQuery = async (db, query, queryParams) => {
var res = {};
try {
const todo_items = await db.client.any(query, queryParams);
res = todo_items;
} catch (e) {
fastify.log.error(e);
}
return res;
};
module.exports = {
queryByAsv
};
Following is the test case. We will mock db.client from the db plugin:
const { queryAll } = require("../src/query");
const any = {
any: jest.fn(() => {
return "mock response";
})
};
describe("should return db query", () => {
beforeAll(async () => {
// set up fastify for test instance
fastify_test = require("fastify")({
logger: {
level: "debug",
file: "./logs/combined.log",
prettyPrint: true
}
});
});
test("test Query All", async () => {
// mock client
const clientPromise = {
client: any
};
//
fastify_test.decorate("db", clientPromise);
const qAll = await queryAll(fastify_test.db);
expect(qAll).toEqual("mock response");
});
});
I'm trying to write a test to checks that when the user clicks on "login" button, the URL is redirected to /auth/. Frontend is written with Vue.js and testing is done with Jest.
Here is how the Vue component redirects (from UserLogged.vue). It works in the browser.
export default {
name: 'UserLogged',
props: ['userName'],
methods: {
login: function (event) {
window.location.href = '/auth/'
}
}
}
and here is the attempt to test it :
import Vue from 'vue'
import UserLogged from '#/components/UserLogged'
describe('UserLogged.vue', () => {
it('should redirect anonymous users to /auth/ when clicking on login button', () => {
const Constructor = Vue.extend(UserLogged)
const vm = new Constructor().$mount()
const button = vm.$el.querySelector('button')
// Simulate click event
// Note: the component won't be listening for any events, so we need to manually run the watcher.
const clickEvent = new window.Event('click')
button.dispatchEvent(clickEvent)
vm._watcher.run()
expect(window.location.href).toEqual('http://testserver/auth/')
})
})
Test output gives "http://testserver/" instead of expected "http://testserver/auth".
I could make the test run nicely with some help https://forum.vuejs.org/t/url-redirection-testing-with-vue-js-and-jest/28009/2
Here is the final test (now written with #vue/test-utils lib) :
import {mount} from '#vue/test-utils'
import UserLogged from '#/components/UserLogged'
describe('UserLogged.vue', () => {
it('should redirect anonymous users to /auth/ when clicking on login button', () => {
const wrapper = mount(UserLogged)
const button = wrapper.find('button')
window.location.assign = jest.fn() // Create a spy
button.trigger('click')
expect(window.location.assign).toHaveBeenCalledWith('/auth/');
})
})
BTW, I had to change window.location.href = '/auth/' to window.location.assign('/auth/') in components/UserLogged.vue.
i'm trying to unit test a controllers method in Angularjs that is responsible for file upload:
$scope.uploadFile = function() {
var fd = new FormData();
for (var i in $scope.files) {
fd.append("uploadedFile", $scope.files[i]);
}
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", $scope.uploadComplete, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.open("POST", "/fileupload");
xhr.send(fd);
}
i tried to mock the xhr object like the following :
it("using $window ", inject(function($window) {
$window.XMLHttpRequest= angular.noop;
addEventListenerSpy = jasmine.createSpy("addEventListener");
openSpy = jasmine.createSpy("open");
sendSpy = jasmine.createSpy("send");
xhrObj = {
upload:
{
addEventListener: addEventListenerSpy
},
addEventListener: addEventListenerSpy,
open: openSpy,
send: sendSpy
};
spyOn($window, "XMLHttpRequest").andReturn(xhrObj);
}));
when i run karma test config file i have the following error :
TypeError: Attempted to assign to readonly property.
at workFn (/home/dre/trunk/app/bower_components/angular-mocks/angular-mocks.js:2107)
can anyone help me i'm new in unit testing with jasmine and karma
You should be using Angular dependency injection which greatly helps tests. You should also use Angular's $http or $resource service to perform XHR requests.
Having said that, I created a fiddle with your test.
Test implementation are missing in your question, e.g. controller creation.
I hope the full example gives you a clue as to the problem in your code.
it("using $window ", function () {
xhrObj = jasmine.createSpyObj('xhrObj',
['addEventListener', 'open', 'send']);
spyOn(window, "XMLHttpRequest").andReturn(xhrObj);
scope.uploadFile()
expect(xhrObj.addEventListener).toHaveBeenCalled();
expect(xhrObj.addEventListener.calls.length).toBe(2);
});
You can find the full example here.
But I urge you to use $http/$resource instead.
I have uses angular translate from here (http://pascalprecht.github.io/angular-translate/) and it's just work fine, but it break my controller's unit test whith Error:
Unexpected request: GET scripts/i18n/locale-en.json
I don't understant why?
I use yeoman and test with karma.
app.js:
'use strict';
(function() {
angular.module('wbApp', ['authService', 'authUserService', 'checkUserDirective', 'ui.bootstrap', 'pascalprecht.translate'])
.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/login.html',
controller: 'LoginCtrl',
access: {
isFree: true
}
})
.when('/main', {
templateUrl: 'views/main.html',
controller: 'MainCtrl',
access: {
isFree: false
}
})
.otherwise({
redirectTo: '/'
});
});
})();
configTranslate.js:
'use strict';
(function() {
angular.module('wbApp')
.config(['$translateProvider',
function($translateProvider) {
$translateProvider.useStaticFilesLoader({
prefix: 'scripts/i18n/locale-',
suffix: '.json'
});
$translateProvider.preferredLanguage('en');
}]);
})();
karma.conf.js:
files = [
...
'app/bower_components/angular-translate/angular-translate.js',
'app/bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.js',
...
];
controller test:
'use strict';
describe('Controller: LoginCtrl', function() {
// load the controller's module
beforeEach(module('wbApp'));
var LoginCtrl, scope, location, httpMock, authUser;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope, $location, $httpBackend, AuthUser) {
authUser = AuthUser;
location = $location;
httpMock = $httpBackend;
scope = $rootScope.$new();
LoginCtrl = $controller('LoginCtrl', {
$scope: scope
});
httpMock.when('GET', 'scripts/i18n/locale-en.json').passThrough();
}));
it(...);
...
});
if i add this in test controller, product same error:
httpMock.when('GET', 'scripts/i18n/locale-en.json').respond(200);
httpMock.flush();
or
httpMock.when('GET', 'scripts/i18n/locale-en.json').passThrough();
httpMock.flush();
i find this post How do I test controllers with Angular Translate initialized in App Config? but not helped me :/
I extensively use $httpBackend in my tests and it works fine, but in this case it is ineffective. If I comment the line:
$translateProvider.preferredLanguage('en');
obviously an error, if I add on the runtime (in my controllers)
$translate.uses(local);
I end up with the same error?
So I turn to the translation configuration (configTranslate.js) or at runtime is the same result:
Unexpected request: GET scripts/i18n/locale-en.json
Here is the syntax that I tested, either in a "beforeEach(inject(function(...});"
or in a test "it('...', function() {...});"
httpMock.expectGET('scripts/i18n/locale-en.json');
httpMock.when('GET', 'scripts/i18n/locale-en.json').passThrough();
httpMock.when('GET', 'scripts/i18n/locale-en.json').respond(data);
with at end
httpMock.flush();
I also tried a $ apply
httpMock.expectGET('scripts/i18n/locale-fr.json');
scope.$apply(function(){
$translate.uses('fr');
});
httpMock.flush();
nothing happens, Still this error is driving me crazy ..
If you have any suggestion
it's a known issue, please follow the documentation here: unit testing angular
The solution
Unfortunately, this issue is caused by the design of
angular-translate. To get around these errors, all we can do is to
overwrite our module configuration in our test suite, that it doesn't
use asynchronous loader at all. When there's no asynchronous loader,
there's no XHR and therefore no error.
So how do we overwrite our module configuration at runtime for our
test suite? When instantiating an angular module, we can always apply
a inline function which is executed as configuration function. This
configuration function can be used to overwrite the modules
configuration since we have access to all providers.
Using the $provide provider, we can build a custom loader factory,
which should then be used instead of the static files loader.
beforeEach(module('myApp', function ($provide, $translateProvider) {
$provide.factory('customLoader', function () {
// loader logic goes here
});
$translateProvider.useLoader('customLoader');
}));
Please read more in the above link provided.
We took the approach of ignoring the translation loader in unit tests, rather than being forced to modify each of the spec files.
One way to do it could be by separating the loader configuration to a separate file and then exclude it in karma.
So for example you can create a file app-i18n-loader.js (all other module configurations takes place in a different file):
angular
.module('myApp')
.config(loaderConfig);
loaderConfig.$inject = ['$translateProvider', '$translatePartialLoaderProvider'];
function loaderConfig($translateProvider, $translatePartialLoaderProvider) {
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: 'assets/i18n/{part}/{lang}.json'
});
$translatePartialLoaderProvider.addPart('myApp');
}
And in your karma.conf.js exclude the file:
files: [
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
//...
'bower_components/angular-translate/angular-translate.js',
'bower_components/angular-translate-loader-partial/angular-translate-loader-partial.js',
'app/**/*.mdl.js',
'app/**/*.js'
],
exclude: [
'app/app-i18n-loader.js'
],
(Note: Answer edited to a solution that does not require grunt/gulp).
I wanted a solution,
which was not too hacky
which didn't require me to change my actual application code,
which wouldn't interfere with the ability to load additional modules
and most importantly which wouldn't require me to change every
single test.
This is what I ended up with:
// you need to load the 3rd party module first
beforeEach(module('pascalprecht.translate'));
// overwrite useStaticFilesLoader to get rid of request to translation file
beforeEach(module(function ($translateProvider) {
$translateProvider.useStaticFilesLoader = function () {
};
}));
Assuming you don't need the actual translations for your unit tests, this works great. Just put the beforeEach on a global level, preferably in it's own file inside the test folder. It will be executed before every other test then.
I encountered this problem with protractor tests. My solution was to mock translations like this:
angular.module('app')
.config(function ($translateProvider) {
$translateProvider.translations('en', {});
$translateProvider.preferredLanguage('en');
})
Now no language files are downloaded, no strings get translated and I just test against the string keys in specifications:
expect(element(by.css('#title')).getText()).toEqual('TITLE_TEXT');
Try putting to test method:
it('should ...', function() {
httpMock.when('GET', 'scripts/i18n/locale-en.json').respond({});
httpMock.expectGET('scripts/i18n/locale-en.json');
scope.resetForm(); // Action which fires a http request
httpMock.flush(); // Flush must be called after the http request
}
See examples from Angular docs
Please have a look at https://github.com/PascalPrecht/angular-translate/blob/master/test/unit/service/loader-static-files.spec.js as a reference.
In general, I would recommend using a standard translation loader for unit tests (without the hassle of http loadings) which means you can provide the labels with $translateProvider.translations(). Why? Because you do not have to test the remote loading functionality which is part of angular-translate project.
None of the solutions worked for me but I came with these solutions:
1) If you need to use scope.$apply(), or should deal with states in your test (after the $apply() the 2nd approach won't work), override your app's translations with the $translateProvider.translations() method, using a plugin to load JSON files
beforeEach(module(function ($translateProvider) {
$translateProvider.translations('en', readJSON('scripts/i18n/locale-en.json'));
}));
2) If your tested controller depends on the $translate service you can use a plugin to load JSON files and combine it with $httpBackend to load your locale file when angular-translate requests it.
beforeEach(inject(function (_$httpBackend_) {
$httpBackend = _$httpBackend_;
$httpBackend.whenGET('scripts/i18n/locale-en.json').respond(readJSON('scripts/i18n/locale-en.json'));
$httpBackend.flush();
})));
Note this should be below your beforeEach(module('myApp')); or you will get an $injector error.
I made a simple mock service for $translate
$translate=function (translation) {
return {
then: function (callback) {
var translated={};
translation.map(function (transl) {
translated[transl]=transl;
});
return callback(translated);
}
}
};
Usage example here : https://gist.github.com/dam1/5858bdcabb89effca457
I use this pattern.
ApplicationModule set regular angular-translate config.
test code load 'testModule' instead of 'applicationModule'
// application module .js
(function() {
'use strict';
angular
.module('applicationModule', [
'ngAnimate',
'ngResource',
'ui.router',
'pascalprecht.translate'
])
.config(['$stateProvider', '$urlRouterProvider', '$translateProvider', '$translatePartialLoaderProvider', config]);
function config($stateProvider, $urlRouterProvider, $translateProvider, $translatePartialLoaderProvider) {
// set routing ...
$translateProvider.useStaticFilesLoader({
prefix: 'i18n/locale-',
suffix: '.json'
});
$translateProvider.useMessageFormatInterpolation();
$translateProvider.fallbackLanguage(['en']);
$translateProvider
.registerAvailableLanguageKeys(['en', 'ko'], {
'en_US': 'en',
'ko_KR': 'ko'
})
.determinePreferredLanguage(navigator.browserLanguage);
$translateProvider.addInterpolation('$translateMessageFormatInterpolation');
$translateProvider.useSanitizeValueStrategy('escaped');
}
})();
// test.module.js
(function() {
'use strict';
angular
.module('testModule', ['applicationModule'])
.config(['$translateProvider', '$translatePartialLoaderProvider', config])
.run(['$httpBackend', run]);
function config($translateProvider, $translatePartialLoaderProvider) {
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: 'i18n/locale-en.json'
});
$translatePartialLoaderProvider.addPart('applicationModule');
}
function run($httpBackend) {
$httpBackend.when('GET', 'i18n/locale-en.json').respond(200);
}
})();
// someDirective.spec.js
describe("a3Dashboard", function() {
beforeEach(module("testModule"))
var element, $scope;
beforeEach(inject(function($compile, $rootScope) {
$scope = $rootScope;
element = angular.element("<div>{{2 + 2}}</div>");
$compile(element)($rootScope)
}))
it('should equal 4', function() {
$scope.$digest();
expect(element.html()).toBe("4");
})
})
Late to the table with this, but I got round this by specifying that Karma simply serve the files as per this entry in karma.conf.js:
files: [
...
{pattern: 'scripts/i18n/*.json', included: false, served: true},
...
]
The 2016 answer for this is to preprocess your json into your tests and properly test translations work on your directives.
I use karma-ng-json2js-preprocessor. Follow all the steps to setup your karma.conf then in your test file, prepend the relevant file as a module, then set that information in $translateProvider.
beforeEach(module('myApp', '/l10n/english-translation.json'));
// Mock translations for this template
beforeEach(module(function($translateProvider, englishTranslation) {
$translateProvider.translations('en_us', englishTranslation);
$translateProvider.useSanitizeValueStrategy(null);
$translateProvider.preferredLanguage('en_us');
}));
Note according to the plugin, it uses your filename to generate a camelcased module name. You can play with the function inside the module's /lib but basically it remove all dashes but KEEPS underscores in a camelCase. So en_us becomes En_us.
You'll also need to tell your test that it is expecting that file as a GEt.
$httpBackend.expect('GET', '/l10n/english-translation.json').respond(200);