Sails.js: Unable to stub a helper for unit testing purposes - unit-testing

Node version: v12.18.3
Sails version (sails): 1.2.3
I am unable to stub a sails helper when performing unit tests. I have a helper that handles all the communication with a database. Moreover, I have an API, which uses this helper. In my tests, I am trying to stub the helper using sinon as such:
The API:
fn: async function (inputs, exits) {
// Stuff done here
// I need to stub this helper
let result = await sails.helpers.arangoQuery.with({
requestId: REQUEST_ID,
query: query,
queryParams: params
});
}
My test:
describe('Get Organization', () => {
it('Server Error - Simulates a failure in fetching the data from ArangoDB', (done) => {
sinon.stub(sails.helpers, 'arangoQuery').returns(null, {status: "success"});
supertest(sails.hooks.http.app)
.get('/organization')
//.expect(200)
.end((error, response) => {
return done()
}
})
})
When I run the test, I get the following error:
error: Error: cannot GET /organization (500)
at Response.toError (/opt/designhubz/organization-service/node_modules/superagent/lib/node/response.js:94:15)
at ResponseBase._setStatusProperties (/opt/designhubz/organization-service/node_modules/superagent/lib/response-base.js:123:16)
at new Response (/opt/designhubz/organization-service/node_modules/superagent/lib/node/response.js:41:8)
at Test.Request._emitResponse (/opt/designhubz/organization-service/node_modules/superagent/lib/node/index.js:752:20)
at /opt/designhubz/organization-service/node_modules/superagent/lib/node/index.js:916:38
at IncomingMessage.<anonymous> (/opt/designhubz/organization-service/node_modules/superagent/lib/node/parsers/json.js:19:7)
at IncomingMessage.emit (events.js:327:22)
at endReadableNT (_stream_readable.js:1220:12)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {
status: 500,
text: '{}',
method: 'GET',
path: '/organization'
}
There are no documentations at all regarding this issue. Can anyone tell me how I can stub a helper?

Sails helpers uses machine, this makes stub making trickier.
AFAIK, the alternative to stub sails helpers is by stubbing the real fn function, because machine will call helper's fn function.
Update: change example that use supertest.
For example:
I create endpoint GET /hello using HelloController,
I use helpers format-welcome-message from helper's example,
I create test spec for endpoint GET /hello.
I run it using mocha without lifecycle.js but embed the lifecycle inside test spec (reference).
Endpoint GET /hello definition:
// File: HelloController.js
module.exports = {
hello: async function (req, res) {
// Dummy usage of helper with predefined input test.
const output = await sails.helpers.formatWelcomeMessage.with({ name: 'test' });
// Just send the output.
res.send(output);
}
};
And do not forget to add route: 'GET /hello': 'HelloController.hello' at config/routes.js.
Test spec contains 3 cases (normal call, stub error, and stub success).
// File: hello.test.js
const sails = require('sails');
const sinon = require('sinon');
const { expect } = require('chai');
const supertest = require('supertest');
describe('Test', function () {
let fwm;
// Copy from example testing lifecycle.
before(function(done) {
sails.lift({
hooks: { grunt: false },
log: { level: 'warn' },
}, function(err) {
if (err) { return done(err); }
// Require helper format welcome message here!
fwm = require('../api/helpers/format-welcome-message');
return done();
});
});
after(function(done) {
sails.lower(done);
});
it('normal case', function (done) {
// Create spy to make sure that real helper fn get called.
const spy = sinon.spy(fwm, 'fn');
supertest(sails.hooks.http.app)
.get('/hello')
.expect(200)
// Expect endpoint output default value.
.expect('Hello, test!')
.end(function() {
// Make sure spy is called.
expect(spy.calledOnce).to.equal(true);
// Restore spy.
spy.restore();
done();
});
});
it('case stub error', function (done) {
// Stub the real fn function inside custom helper.
const stubError = sinon.stub(fwm, 'fn');
stubError.callsFake(async function (input, exits) {
// Setup your error here.
exits.error(new Error('XXX'));
});
supertest(sails.hooks.http.app)
.get('/hello')
.expect(500)
.end(function() {
// Make sure stub get called once.
expect(stubError.calledOnce).to.equal(true);
// Restore stub.
stubError.restore();
done();
});
});
it('case stub success', function (done) {
// Define fake result.
const fakeResult = 'test';
// Stub the real fn function inside custom helper.
const stubSuccess = sinon.stub(fwm, 'fn');
stubSuccess.callsFake(async function (input, exits) {
// Setup your success result here.
exits.success(fakeResult);
});
supertest(sails.hooks.http.app)
.get('/hello')
// Expect endpoint to output fake result.
.expect(fakeResult)
.end(function() {
// Make sure stub get called once.
expect(stubSuccess.calledOnce).to.equal(true);
// Restore stub.
stubSuccess.restore();
done();
});
});
});
When I run it using mocha:
$ npx mocha test/hello.test.js
Test
✓ normal case
error: Sending 500 ("Server Error") response:
Error: XXX
at Object.<anonymous> ...
✓ case stub error
✓ case stub success
3 passing (407ms)
$

Related

Using different jest mocks of a class for separate unit tests

I am working with AWS and writing some unit tests for a class that makes use of AWS's Secrets Manger service. For those interested, I'll link the documentation to Secrets Manager here: https://docs.aws.amazon.com/secretsmanager/index.html
On to the question: I am trying to write two unit tests for a class that uses Secrets Manager, specifically the 'getSecretValue' function. The gist of my code is below:
Consumer Function File: secretValueManager.ts
import { SecretsManager } from 'aws-sdk';
const manager = new SecretsManager({});
export default async function retrieveSecretValue(secretId: string) {
try {
const result = await manager.getSecretValue({ secretId });
return {
value: result.SecretString,
};
} catch (err: any) {
throw new Error('encountered an error retrieving secret');
}
};
Spec/Test file: secretValueManager.spec.ts
import retrieveSecretValue from './secretValueManager';
// MOCK #1
jest.mock('aws-sdk', () => {
return {
__esModule: true,
SecretsManager: function (): any {
return {
getSecretValue: function (): any {
return {
promise: jest.fn().mockImplementation(() => ({
SecretString: 'returned signingKey',
})),
};
},
};
},
};
});
// MOCK #2
jest.mock('aws-sdk', () => {
return {
__esModule: true,
SecretsManager: function (): any {
return {
getSecretValue: function (): any {
return {
promise: jest.fn().mockImplementation(() => {
throw new Error('error_message');
}),
};
},
};
},
};
});
describe('secretsManager', () => {
it('should get secret value', async () => {
await expect(retrieveSecretValue('secretid')).resolves.toBe({ value: 'returned signingKey' });
});
it('should re-throw error with custom message', async () => {
await expect(retrieveSecretValue('secret id')).rejects.toThrow('encountered an error retrieving secret');
});
});
As you can see, mock #1 is meant to test the 'try' block with the first individual test, and mock #2 is meant to test the 'catch' block with the second individual test. Obviously, I can't call jest.mock() twice and change the mock on a per test basis.
What I am used to doing is importing functions on their own and making use of the jest function mockImplementationOnce(), but the 'getSecretValue' function is not an exported function and only exists on instances of the 'SecretsManager' class.
I know that mock #1 results in test 1 passing, and that mock #2 results in test 2 passing, but how can I use both of these mocks on a per test basis?

Im trying to mock a function from a service but Jest keeps calling the actual function instead of the mock function

I'm using Jest to test a function from a service that uses axios to make some api calls. The problem is that Jest keeps calling the actual services function instead of the mocked service function. Here is all of the code:
The tests:
// __tests__/NotificationService.spec.js
const mockService = require('../NotificationService').default;
beforeEach(() => {
jest.mock('../NotificationService');
});
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
expect.assertions(1);
const data = await mockService.fetchNotifications();
console.log(data);
expect(data).toHaveProperty('data.bell');
});
});
The mock:
// __mocks__/NotificationService.js
const notifData = {
bell: false,
rollups: [
{
id: 'hidden',
modifiedAt: 123,
read: true,
type: 'PLAYLIST_SUBSCRIBED',
visited: false,
muted: false,
count: 3,
user: {
id: 'hidden',
name: 'hidden'
},
reference: {
id: 'hidden',
title: 'hidden',
url: ''
}
}
],
system: [],
total: 1
};
export default function fetchNotifications(isResolved) {
return new Promise((resolve, reject) => {
process.nextTick(() =>
isResolved ? resolve(notifData) : reject({ error: 'It threw an error' })
);
});
}
The service:
import axios from 'axios';
// hardcoded user guid
export const userId = 'hidden';
// axios instance with hardcoded url and auth header
export const instance = axios.create({
baseURL: 'hidden',
headers: {
Authorization:
'JWT ey'
}
});
/**
* Notification Service
* Call these methods from the Notification Vuex Module
*/
export default class NotificationService {
/**
* #GET Gets a list of Notifications for a User
* #returns {AxiosPromise<any>}
* #param query
*/
static async fetchNotifications(query) {
try {
const res = await instance.get(`/rollups/user/${userId}`, {
query: query
});
return res;
} catch (error) {
console.error(error);
}
}
}
I've tried a couple of variations of using require instead of importing the NotificationService, but it gave some other cryptic errors...
I feel like I'm missing something simple.
Help me please :)
The problem is that Jest keeps calling the actual services function instead of the mocked service function.
babel-jest hoists jest.mock calls so that they run before everything else (even import calls), but the hoisting is local to the code block as described in issue 2582.
I feel like I'm missing something simple.
Move your jest.mock call outside the beforeEach and it will be hoisted to the top of your entire test so your mock is returned by require:
const mockService = require('../NotificationService').default; // mockService is your mock...
jest.mock('../NotificationService'); // ...because this runs first
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
...
});
});

Angular 6 - Jasmine - mock httpClient get map and error flows

I'm new to Angular testing and am trying to figure out how to write a test that mocks an error response of HttpClient.get() function. Basically my service has both map() and catchError() inside of its pipe() and I would like to excercise both flows. Here's what I have so far:
my.service.ts:
getItems(): Observable<ItemViewModels[]> {
return
this.httpClient.get<any>(this.getItemsUrl)
.pipe(
map(json => {
return json.map(itemJson => this.getVmFromItemJson(itemJson));
}),
catchError(() => {
// Log stuff here...
return of(null);
})
);
}
my.service.spec.ts:
it('should catch error and return null if API returns error', () => {
spyOn(service.httpClient, 'get').and.returnValue(new Observable()); // Mock error here
service.getItems().subscribe($items => expect($items).toBe(null));
});
it('should return valid view model array if API returns a valid json', () => {
const mockResponse = [
new SideNavItemViewModel(1),
new SideNavItemViewModel(2),
new SideNavItemViewModel(3)
];
spyOn(service.httpClient, 'get').and.returnValue(of(JSON.stringify(mockResponse)));
service.getSidenavViewModel().subscribe(x => expect(x).toBe(mockResponse));
});
So the actual issue is that the observables that I mock for the httpClient to return on get in the unit tests don't seem to get into the .pipe() function, which means that my tests aren't working :(
Any ideas?
Thanks!
Have you tried injecting your service into the test? I also try test the function that subscribes to the api call instead of creating another subscribe:
for errors:
it('should display error when server error occurs',
inject([HttpTestingController, AService],
(httpMock: HttpTestingController, svc: MyService) => {
svc.getItems(); // has the subscribe in it
const callingURL = svc['API']; // your api call eg. data/items
httpMock.expectOne((req: HttpRequest < any > ) => req.url.indexOf(callingURL) !== -1)
.error(new ErrorEvent('Customer Error', {
error: 500
}), {
status: 500,
statusText: 'Internal Server Error'
});
httpMock.verify();
expect(component.svc.Jobs).toBeUndefined();
fixture.detectChanges();
// UI check here
}));
data test
it('should display the correct amount of data elements',
inject([HttpTestingController, AService],
(httpMock: HttpTestingController, svc: MyService) => {
svc.getItems(); // has the subscribe in it
const callingURL = svc['API']; // your api call eg. data/items
const mockReq = httpMock.expectOne((req: HttpRequest < any > ) => req.url.indexOf(callingURL) !== -1);
mockReq.flush(mockData);
httpMock.verify();
expect(component.svc.data.length).toBe(mockData.length);
fixture.detectChanges();
// UI check here
}));
So basically these functions:
call your get and subscribe
checks that your api url is contained in the http call
mocks the response - you pass in the data - 'mockData'
mockReq.flush(mockData); will trigger the call
httpMock.verify(); will check the url and other things
now the service data can be tested - if you subscribe sets anything in there
fixture.detectChanges(); - then this will allow to test ui components
I prefer this way because you can keep your logic and tests separate.

sinon stub error "attempted to wrap undefined property of job as function"

I am trying to use sinon stub to test my function that contains two variables called job and job1. How to give temporary values to them to avoid function values.
In one of the file myFunction.js I have functions like
function testFunction() {
var job = this.win.get.value1 //test
var job1 = this.win.get.value2 // test1
if(job === 'test' && job1 === 'test1') {
return true;
}
return false;
}
and I am trying to test testFunction using karma and I tried to stub two values with my values so it can override the function values
it('should test my function', function(done) {
var stub = sinon.stub('job','job1').values('test','test1');
myFunction.testFunction('test', function(err, decodedPayload) {
decodedPayload.should.equal(true);
done();
});
});
I am getting error "attemted to wrap undefined property of job as function"
First of all you could simplify your testFunction to the following.
function testFunction() {
return this.win.get.value1 === 'test' && this.win.get.value2 === 'test1';
}
There's nothing asynchronous going on here so in your test you don't need to use done().
Sinon's 'stub' documentation suggests you should be using the sandbox feature to stub non-function properties.
It's not clear from your question what your context of 'this' is so I'll assume your tests have instantiated whatever it is you're testing with the name 'myFunction' (which your test implies).
It's also unclear what 'win' and 'get' are so this will assume they are objects.
Don't forget to restore() the sandbox so you don't pollute subsequent tests.
it('should test my function', function() {
var sandbox = sinon.sandbox.create();
sandbox.stub(myFunction, 'win').value({
get: {
value1: 'test',
value2: 'test1',
}
});
myFunction.testFunction().should.equal(true);
sandbox.restore();
});

How do I unit test a helper that uses a service?

I'm trying to unit test a helper that uses a service.
This is how I inject the service:
export function initialize(container, application) {
application.inject('view', 'foobarService', 'service:foobar');
}
The helper:
export function someHelper(input) {
return this.foobarService.doSomeProcessing(input);
}
export default Ember.Handlebars.makeBoundHelper(someHelper);
Everything works until here.
The unit test doesn't know about the service and fails. I tried to:
test('it works', function(assert) {
var mockView = {
foobarService: {
doSomeProcessing: function(data) {
return "mock result";
}
}
};
// didn't work
var result = someHelper.call(mockView, 42);
assert.ok(result);
});
The error:
Died on test #1 at http://localhost:4200/assets/dummy.js:498:9
at requireModule (http://localhost:4200/assets/vendor.js:79:29)
TypeError: undefined is not a function
Everything is correct, the only change needed was:
var result = someHelper.call(mockView, "42");