How do I completely mock an imported service class - javascript

I am looking to test my next API route which uses the micro framework (similar enough to express when using next-connect).
I have a service:
export class UserService {
constructor(...) {}
async findUser({ email }: Pick<IUserModel, 'email'>) {
...
}
}
My API endpoint:
import { NextApiRequest, NextApiResponse } from 'next';
import nc from 'next-connect';
import { UserService } from './UserService'; // Mock and prevent execution
const userService = new UserService();
export default nc().post(async (req: NextApiRequest, res: NextApiResponse) => {
try {
userService.findUser({ email: 'john.doe#example.com' });
return res.status(200).send({ done: true });
} catch (error: any) {
return res.status(500).end(error.message);
}
});
I would like to completely mock the import and prevent any dependent code from executing.
import { UserService } from './UserService';
For example if there is a console.log() in the UserService constructor it should not be run because the import is a completely mocked import.
Update:
I've attempted to use jest.mock but they didn't seem to actually mock my imports. I've added a console.log in the UserService constructor and it continues to be triggered when using jest.mock.
import signup from './signup';
jest.mock('./UserService');
describe('signup', () => {
it('should complete the happy path', async () => {
const req: IncomingMessage = {} as unknown as IncomingMessage;
const res: ServerResponse = {
end: jest.fn(),
} as unknown as ServerResponse;
const actual = await signup(req, res);
expect(actual).toBe(...);
});
});

If you mean mocking in a jest test then you can just use jest.mock('./userService'); or jest.spyOn(UserService, 'findUser') if you want to mock one method.
If you want a mock for a specific use case, you would create a mock service and conditionally import based on some flag.
Ex:
// UserService.js
export class UserService {
constructor(...) {}
async findUser({ email }: Pick<IUserModel, 'email'>) {
// calls real API
}
}
// UserService.mock.js
export class MockUserService {
constructor(...) {}
async findUser({ email }: Pick<IUserModel, 'email'>) {
// calls fake API or just returns a promise or anything you want
}
}
// Your Endpoint
import { NextApiRequest, NextApiResponse } from 'next';
import nc from 'next-connect';
import { UserService } from './UserService';
import { MockUserService } from './UserService.mock'; // Mock
let userService;
if (someCondition) {
userService = new UserService();
} else {
userService = new MockUserService();
}
...
// The idea is you want to dynamically change what you're importing
const Something = process.env.NODE_ENV === 'development' ?
require('./FakeSomething') : require('./RealSomething');

jest.mock doesn't support mocking dependencies for imports outside of the actual test file (.spec.ts & .test.ts). In my case I have a lot of dependencies in my Next API endpoint that cannot be mocked.
You'll have to find a pattern to inject the dependency into the endpoint and test it in isolation instead. I made use of the Nextjs middleware example here but there are likely other patterns that will work just as well.
// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function runMiddleware(req: NextApiRequest, res: NextApiResponse, fn: Function) {
return new Promise((resolve, reject) => {
fn(req, res, (result: any) => {
if (result instanceof Error) {
return reject(result)
}
return resolve(result)
})
})
}
// Testable function for DI
export function post(
_userService: UserService,
) {
return async function(req: NextApiRequest, res: NextApiResponse) {
...
}
}
// Endpoint
export default async function(req: NextApiRequest, res: NextApiResponse) {
try {
await runMiddleware(req, res, post(userService));
return res.status(200).send({ done: true });
} catch (error: any) {
return res.status(500).end(error.message);
}
};
// Test usage
describe('signup', () => {
let userService: UserService;
beforeAll(() => {
userService = new UserService({} as unknown as UserRepository);
});
it('should complete the happy path', async () => {
const findSpy = jest
.spyOn(userService, 'find')
.mockImplementation(async () => null);
const req: NextApiRequest = {
body: {
email: 'user#test.com',
password: '12345678',
},
} as unknown as NextApiRequest;
const res: NextApiResponse = {} as unknown as NextApiResponse;
await post(userService)(req, res);
...
});
});

Related

How can I mock a class using jest?

How can I mock something to test something like the following codes. I tried to follow this official doc, but still not working for me https://jestjs.io/docs/es6-class-mocks#calling-jestmock-with-the-module-factory-parameter
// somefile.ts
export const myPublish = async (event: any, context: any): Promise<any> => {
const myExportHelper = await ExportHelper.getInstance({
...commonProps,
});
// just some other stuff
// just some other stuff
await myExportHelper.transfer(arg1, arg2);
};
export class ExportHelper {
constructor(
private readonly bucket: string,
private readonly read: AWS.S3,
private readonly write: AWS.S3
) {}
static async getInstance(props: {
param1: string;
}) {
...
...
return new ExportHelper(arg1, arg2, arg3);
};
async transfer(param1, param2) {
...
...
console.log('bla bla bla');
}
}
// testfile.test.ts
import { myPublish, ExportHelper } from '../somefile';
beforeEach(() => {
});
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('myTest', () => {
it('should run successfully', async () => {
// Arrange
const eventMock = {
Records: [
{
...
}
]
}
jest.mock('../somefile');
const mockActualExportHelper = jest.requireActual('../somefile').ExportHelper;
const mockGetInstanceImpl = () => {};
// this says cannot read property instances of undefined
const mockExportHelper = mockActualExportHelper.mock.instances[0];
mockExportHelper.getInstance.mockImplementation(mockGetInstanceImpl);
mockExportHelper.transfer.mockImplementation(mockGetInstanceImpl);
// Act
await myPublish(eventMock, jasmine.any({}));
// Assert
expect(ExportHelper.getInstance).toBeCalled();
expect(ExportHelper.transfer).toBeCalled(); // also not sure if this is valid to use ExportHelper
});
});
I think what you're looking for is not a mock. If you want to spy what functions are called, you will need to use the spyOn. In jest you can do the following:
jest.spyOn(MyClass, 'myMethod');
And you can also mock the implementation to subtitute the default behavior of a method, a generalist example can be like this:
jest.spyOn(MyClass, 'myMethod').mockImplementation(jest.fn());
With that said, I would rewrite the test to spy the methods from ExportHelper and avoid external calls:
import {ExportHelper, myPublish} from '../app';
beforeEach(() => {
});
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('myTest', () => {
it('should run successfully', async () => {
// Arrange
const eventMock = {
Records: [
{
// ...
}
]
}
jest.spyOn(ExportHelper, 'getInstance').mockImplementation(jest.fn());
jest.spyOn(ExportHelper, 'transfer').mockImplementation(jest.fn());
// Act
await myPublish('arg1', 'arg2');
// Assert
expect(ExportHelper.getInstance).toBeCalled();
expect(ExportHelper.transfer).toBeCalled();
});
});
I just replaced this piece of code:
jest.mock('../somefile');
const mockActualExportHelper = jest.requireActual('../somefile').ExportHelper;
const mockGetInstanceImpl = () => {};
// this says cannot read property instances of undefined
const mockExportHelper = mockActualExportHelper.mock.instances[0];
mockExportHelper.getInstance.mockImplementation(mockGetInstanceImpl);
mockExportHelper.transfer.mockImplementation(mockGetInstanceImpl);
with
jest.spyOn(ExportHelper, 'getInstance').mockImplementation(jest.fn());
jest.spyOn(ExportHelper, 'transfer').mockImplementation(jest.fn());
Because jest will track down and watch any of these method's calls and then we can use jest's matchers to test if both of them were called. And the mockImplementation will isolate any further calls to be maded.
One thing that I noticed while reproducing your example, is that the transfer method is not being treated as a method when you get the instance from getInstance and therefore, the tests will not pass. But I think this question is not in the scope of the topic. Dunno if just happens to me.

Promises: I would like to use the return value of a promise to create an instance and export instance as a module

I would like to assign the instance of PusherPushNotifications.Client into a variable beamsClient and export the module. PusherPushNotifications.Client requires the serviceWorkerRegistration as part of the options
inside pusher.js, I have the following but beamsClient:
import * as PusherPushNotifications from '#pusher/push-notifications-web'
export default function tokenProvider() {
return new PusherPushNotifications.TokenProvider({
url: 'http://test-backend.com/beams-auth',
queryParams: {
},
headers: {
Authorization: 'FooBarBazToken'
}
})
}
const init = async () => {
await window.navigator.serviceWorker.ready.then(serviceWorkerRegistration => {
beamsClient = new PusherPushNotifications.Client({
instanceId: 'INSTANCE_ID',
primaryKey: 'PRIMARY_KEY',
serviceWorkerRegistration
})
})
}
// ------->>> I need the instance of Client here
let beamsClient = init()
console.log('Beams', beamsClient)
export { beamsClient }
You cannot export a promise result. You could, though, export the promise and have whatever's importing the file await it from there.

How to stub exported function from module in cypress?

I'm not able to see stubbed response for getName method while invoking getUser from Cypress. Is there a way to correct this?
// Service.ts
export const getName = (): string => {
return name;
}
// User.ts
import {getName} from './Service'
export const getUser = (): User => {
const name = getName();
// ... rest of code for User creation
}
// User.cypress.ts
import * as service from './Service'
it('tests user', () => {
cy.stub(service, 'getName').returns('abc');
cy.get('#get-user-id').click();
});
You may need to change the way the function is exported from Service.ts.
Try adding a default export to the module.
// Service.ts
const getName = (): string => {
return name;
}
module.exports {
getName
}
// User.cypress.ts
import service from './Service'
it('tests user', () => {
cy.stub(service, 'getName').returns('abc');
cy.get('#get-user-id').click();
});

Why my stub Sinon.Stub is ignored?

I'm wondering why my stub is ignored.
Let say I have a file called myfile.ts which exports two async methods. A and B.
import { sendResult } from '../index'
export async A(): Promise<string[]> {
// making an async api call
const result = await apiCall()
// do sync treatement on result then send it back
...
return value
}
export async B(): Promise<void> {
// Calling A()
const aResult = await A()
// do some sync treatement and call a method from an other module than
// returns a Promise<void>
...
sendResult()
}
I need to unit test my B method and stub A and sendResult
My testFile.ts looks like
import { sandbox } from 'sinon'
import * as AB from './modules/ab'
import * as INDX from './index'
const testSandbox = sandbox.create()
describe('my tests', function () {
beforeEach(() => {
testSandbox.stub(INDX, 'sendResult').resolves()
testSandbox.stub(AB, 'A').resolves([])
})
afterEach(() => {
testSandbox.restore()
})
it('should pass but it is not', async function (done) {
await AB.B()
const sendResultStub = UL.sendResult as SinonStub
assert.calledOnce(sendResultStub)
done()
})
})
I do not understand why sendResult is well stubed but A is not. What do I miss?
Thanks is advances folks!
Bastien

Angular observable post and subscribe explanation / How it works

Explanation of Observable Post
setup.component.ts
import { Component, EventEmitter, OnInit, Output } from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import { Post } from './model/post';
import { PostService } from './service/post.service';
#Component({
selector: 'setup',
templateUrl: './setup.component.html',
styleUrls: ['./setup.component.scss']
})
export class SetupComponent implements OnInit {
#Output()
change: EventEmitter<string> = new EventEmitter();
postUsers(input){
this.postService.postUser(input)
.subscribe(
post => {
this.post = post
},
err => {
console.log(err);
});
}
clicked(value) {
console.log(value);
this.postUsers(this.input)
// this.change.emit(value);
}
complexForm : FormGroup;
constructor(private postService: PostService) {}
post: Post[];
ngOnInit() {}
}
post.service.ts
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Post } from '../model/post';
import { Observable } from 'rxjs/Rx';
// Import RxJs required methods
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class PostService {
constructor (private http: Http) {}
private registerUrl = 'http://localhost:2001/api/users/register';
postUser(user:Post) : Observable<Post[]>{
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post(this.registerUrl, user, options)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
return body || { };
}
private handleError (error: Response | any) {
// In a real world app, you might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}
model/post.ts
export class Post {
constructor(
public id: number,
public name: string,
public email: string,
public password: string
){}
}
I understand what the model/post.ts does, it defines the property types, what I need is clarification on the subscribe method within setup.components.ts. The Observable gets invoked inside clicked() but what I want to understand is how does this because accessible to the application so that I can proceed after the action has run its course this.postUsers(this.input)
Typically with a Promise I would have done the following
this.postUsers(this.input)
.then(function(){
});
I'd really like it if someone could explain how it works and how to achieve a confirmation that the post has completed to then run the next function
ie I have this
clicked(value) {
console.log(value);
this.postUsers(this.input)
// this.change.emit(value);
}
but in promise I'd do
clicked(value) {
console.log(value);
this.postUsers(this.input)
.then(function(){
this.change.emit(value);
});
}
How can I get this to work with Observables? I tried to see what was returned by doing
clicked(value) {
console.log(value);
const runThis = this.postUsers(this.input);
console.log(runThis);
// this.change.emit(value);
}
but it returns undefined.
Similar with promises make your postUsers method return an observable (not a subscription)
postUsers(input){
return this.postService.postUser(input);
}
Then you can subscribe to this method like using a then in promises.
clicked(value) {
console.log(value);
this.postUsers(this.input).subscribe((response)=> {
this.change.emit(value);
});
}
You can also convert observables into promises. Then you wouldn't get confused.
import 'rxjs/add/operator/toPromise';
postUsers(input){
return this.postService.postUser(input).toPromise();
}
clicked(value) {
console.log(value);
this.postUsers(this.input)
.then((res)=>{
this.change.emit(value);
});
}
Note that I haven't used the function keyword in my callbacks. If I did this.change would refer to the callbacks change because the this wouldn't refer to the component.
how to achieve a confirmation that the post has completed
When the stream triggers complete callback you will know the request has completed. This is the third callback you pass to the subscribe method or the complete method if you pass observer object instead of callbacks.
I think you will benefit from knowing how the operator toPromise work. You can then use it directly or emulate its logic. You can use it:
this.postUsers(this.input).toPromise()
.then(function(){
...
});
Basically what it does is creates a promise and returns it. Then it subscribes the the observable returned by the this.postUsers and waits until this observable emits completed event. After that, it resolves the promise with the last event it returned. Something along these lines:
toPromise(observable) {
let lastReturnedValue = null;
return new Promise() {
observable.subscribe(
(v)=>{
lastReturnedValue = v;
},
(e)=>{
reject(e);
},
(c)=>{
resolve(lastReturnedValue);
});
}
}
So just as with toPromise you can subscribe to the observable and listen for complete event:
clicked(value) {
console.log(value);
this.postUsers(this.input).subscribe(
(v)=>{ // here you get the value and can save it },
(e)=>{ // here you know that observable has error, similar to `catch` callback },
()=>{ // here you know that observable has completed, similar to `then` callback}
);
console.log(runThis);
// this.change.emit(value);
}
Or you can return the observable for someone else to listen:
clicked(value) {
return this.postUsers(this.input)
}
...
clicked().subscribe( // all the code from previous example)

Categories

Resources