I have a component class as below which create the rest and websocket connections using a third party npm module. I could change the Component.constructor to accept the module as a dependency so that I can inject a mock version during my Jest testing. But I read about Mocks with Jest, I thought I want to try it, but I cannot seem to understand how to intercept Api.Rest() and Api.Websocket return values.
// component.ts
import * as Api from 'npm-module'
import * as wait from 'wait-for-stuff' // actual npm module
export class Component {
private _rest:any;
private _websocket:any;
public events = new EventEmitter();
constructor() {
// I want to intecept the return value of
// Api.Rest() and Api.Websocket() to use mock versions.
this._rest = new Api.Rest();
this._websocket = new Api.Websocket();
this._init();
}
private _init() {
// so that when do stuff with this._rest and this._websocket;
// I can control what is the expected results during test
this._websocket.onUpdate((data) => {
events.emit('update', data);
});
var value = wait.for.promise(this._rest.getSomething());
}
}
Do I have to use another test library like Sinon or Jasmine?
Here is a simplified working example to get you started:
// #ts-ignore
import * as Api from 'npm-module'; // <= (ts-ignore since "npm-module" doesn't exist)
import EventEmitter from 'events';
jest.mock('npm-module', () => {
const getSomethingMock = jest.fn(); // <= always return...
const onUpdateMock = jest.fn(); // <= ...the same mocks...
return {
Rest: () => ({ getSomething: getSomethingMock }),
Websocket: () => ({ onUpdate: onUpdateMock })
}
},
{ virtual: true }); // <= (use virtual since "npm-module" doesn't exist)
class Component {
private _rest: any;
private _websocket: any;
public events = new EventEmitter();
constructor() {
this._rest = new Api.Rest();
this._websocket = new Api.Websocket();
this._init();
}
private _init() {
this._websocket.onUpdate((data) => { // <= ...so that this onUpdate...
this.events.emit('update', data);
});
}
}
test('Component', () => {
const component = new Component();
const listener = jest.fn();
component.events.on('update', listener);
const onUpdate = new Api.Websocket().onUpdate; // <= ...is the same as this one
const onUpdateArrowFunction = onUpdate.mock.calls[0][0]; // <= get the arrow function passed to it
onUpdateArrowFunction('mock data'); // <= now call the function
expect(listener).toHaveBeenCalledWith('mock data'); // Success!
});
Details
Jest takes over the require system and allows you to specify what you want it to return when a module is required (note that TypeScript import statements get compiled to require calls).
One way to mock a module is to create a manual mock by creating a file at __mocks__/npm-module.ts that contains your mock.
Another way (show above) is to use jest.mock and pass it a module factory function.
Whenever the module is required during the test Jest will return the mocked module instead.
Note that the example above always returns the same mock for getSomething and onUpdate so those mock functions can be retrieved during the test.
Also note the use of mockFn.mock.calls to retrieve this arrow function:
(data) => {
this.events.emit('update', data);
}
...which gets passed to onUpdate. Once it has been retrieved, it can be called directly which triggers the listener as expected.
Related
my setup looks like this
const sandbox = sinon.createSandbox();
test.afterEach.always(() => {
sandbox.restore();
});
test.serial( 'some test', t => {
new awvr(context)
})
test.serial( 'other test', t=> {
new awvr(other_context)
}
export class awvr {
private static isStubConfigured: boolean = false;
private readonly serviceContext: ServiceCtx;
constructor(ctx: ServiceCtx) {
this.serviceContext = ctx;
awvr.setupMockedIntegrationRequestHandler();
}
private static setupMockedIntegrationRequestHandler() {
if (!awvr.isStubConfigured) {
const requestStub = { post: sandbox.stub() };
requestStub.post.resolves({ body: { output: 'test-response-field' } });
const integrationRequestStub = sandbox.stub(requestUtils, 'getIntegrationRequest');
integrationRequestStub.returns((requestStub as unknown));
const mockLock = createMockRedlock();
sandbox.stub(redis, 'lockNode').resolves(mockLock);
awvr.isIntegrationStubConfigured = true;
}
}
when i run these tests i get an error saying the below along with stack trace
attempted to wrap x which is already wrapped
checkWrappedMethod
wrapMethod
stub
Sandbox.stub
Function.setupMockedIntegrationRequestHandler
new awvr
processTicksAndRejections
test.ts:601:3
wrapMethod
stub
Sandbox.stub
Function.setupMockedIntegrationRequestHandler
new awvr
processTicksAndRejections
This seems to imply there's some parallelization going on -- although i tried creating two separate sandboxes for each test and passing them through to be used by awvr and that didn't work either. I have tried calling sandbox.restore() in the awvr call as well, but issue persists -- Is there something sinon does under the hood that doesn't allow for this setup?
I am trying testing on node js I want to mock a variable outside of a function that is going to be unit tested.
for example
const sample = [];
function uploadDoc {
sample.push('fileLocation')
}
function toSave(){
for (i=0;i<sample.length;i++)
**some process read and access doc
}
I am facing issue while unit testing the second function
I have tried rewire npm that is also not working
Programming likes this will work,You should call uploadDoc firstly.
const sample = [];
function uploadDoc {
sample.push('fileLocation')
}
function toSave(){
uploadDoc();
for (var i=0;i<sample.length;i++)
**some process read and access doc
}
Well, you face challenges to test both functions, then, what you need is to refactor the code. Because I think, your code is not testable.
I give you this long example, which contain both of your functions but testable.
Changes I made for refactor:
function uploadDoc now will output an array.
function toSave now will require 1 parameter, which is an array.
combine functions into class.
const sinon = require('sinon');
const { expect } = require('chai');
const Doc = {
/**
* #return array of file location.
*/
uploadDoc: () => {
const sample = [];
sample.push('fileLocation');
return sample;
},
/**
* #param array samples
* #return void
*/
toSave: (samples) => {
samples.forEach(() => {
// Some process read and access doc.
})
},
}
/**
* This is for example purposes.
* #return void.
*/
function integrationBothFunction () {
const samples = Doc.uploadDoc();
Doc.toSave(samples);
}
describe('Unit Test', function () {
it('uploadDoc fn', function () {
const result = Doc.uploadDoc();
// You expect the result to be an array.
expect(result).to.be.an('array');
});
it('toSave fn', function () {
const samples = [];
const spySamples = sinon.spy(samples, 'forEach');
// Call the function
Doc.toSave(samples);
// You expect: there is forEach called for samples.
expect(spySamples.calledOnce).to.equal(true);
// Do not forget to restore.
spySamples.restore();
});
it('integrationBothFunction fn', function () {
// This is just sample to check.
const testSample = ['x'];
// Now finally, you can stub uploadDoc.
// Where real uploadDoc function not get called.
const stubUploadDoc = sinon.stub(Doc, 'uploadDoc');
stubUploadDoc.returns(testSample);
// Spy on toSave fn.
const spyToSave = sinon.spy(Doc, 'toSave');
integrationBothFunction();
// You expect that toSave is processing output from uploadDoc.
expect(stubUploadDoc.calledOnce).to.equal(true);
expect(spyToSave.calledOnce).to.equal(true);
// Check argument toSave.
const { args } = spyToSave;
expect(args).to.have.lengthOf(1);
expect(args[0]).to.have.lengthOf(1);
expect(args[0][0]).to.include(testSample[0]);
// Restore spy and stub.
stubUploadDoc.restore();
spyToSave.restore();
});
});
I you run using mocha:
$ npx mocha stackoverflow.js
Unit Test
✓ uploadDoc fn
✓ toSave fn
✓ integrationBothFunction fn
3 passing (13ms)
$
Hope this helps. Good luck!
I'm having some issues using sinon stubs and it may stem from how I'm implementing namespacing on the module that I'm looking to stub. Methods directly defined on the prototype are stubbed as I would expect.
...my module.js
const Constructor = require('./constructor') //...just exports a singleton
/* Need to namespace some of my functions and retain the `this` context */
Object.defineProperty(Constructor.prototype, 'es', {
get: function() {
return {
method: require('./implementations/doesSomething.js').bind(this)
}
}
});
module.exports = Constructor;
/* ...testFile.js */
const Constructor = require('./constructor');
const instance = new Constructor();
const sinon = require('sinon');
sinon.stub(instance.es, 'method', function() {
return 'hijacked original method'
});
As mentioned on the Sinon issue tracker, the problem here is that using a plain Object.defineProperty(obj, 'prop') call does something else than plainly creating it using assignment (obj['prop'] = ...).
Generally, if you try defining your property without Object.defineProperty it will be stubbable, but using defineProperty (without creating a special configuration) will make it impossible to stub the property. The reason is simply that the default values for writeable and configurable are false! You cannot delete them or change them. And if you can't do that, then Sinon won't help you. So, generally, you need to add writeable: true, configurable: true in your property definition.
Now there was one more thing I forgot to answer originally:
You are not trying to wrap a function on Constructor.prototype.es.method - what you are trying to wrap is the function on the object returned by the getter on the property for es. That will never work. Why? Simply because the returned object is never the same. You are creating a new object around method each time. If you really need to replace/stub the method property, you actually need to replace the entire Constructor.prototype.es property instead. If you need this namespacing, you can vastly simplify this, and also enable stubbing, like this:
Constructor.prototype.es = {};
Object.defineProperty(Constructor.prototype.es, 'method', {
get: function() {
return someFunction.bind(this);
},
writeable: true,
configurable:true
}
An expanded, fully working example (Gist for download):
// constructor.js
const someFunction = function(){
return this.value;
}
function Constructor(){ };
Constructor.prototype.es = { value : 100 };
Object.defineProperty(Constructor.prototype.es, 'method', {
get: function() {
return someFunction.bind(this);
},
writeable: true,
configurable:true
});
// test.js
const instance = new Constructor();
console.log(instance.es.method()) // => 100
// using this won't work:
// sinon.stub(instance.__proto__.es, 'method').returns(42);
// because the getter is returning a _new_ function each time
// therefore you need to attack the actual getter function:
const stub = sinon.stub(instance.__proto__.es, 'method').value(()=>42);
console.log(instance.es.method()) // => 42
stub.get(()=>()=>84);
console.log(instance.es.method()) // => 84
stub.restore();
console.log(instance.es.method()) // => 100
// the above is working on the prototype, can't we do this on the instance?
// yes, we can, but remember that the `es` object is shared, so we
// can avoid modifying it by shadowing it further down the prototype
instance.es = { method: sinon.stub().returns(256) };
console.log(instance.es.method()) // => 256
delete instance.es
console.log(instance.es.method()) // => 100
<script src="https://unpkg.com/sinon#2.3.5/pkg/sinon.js"></script>
I am writing a custom middleware that needs to dispatch thunk actions. The problem is that the middleware is called after redux-thunk in the middleware chain, so I get the error Uncaught Error: Actions must be plain objects. Use custom middleware for async actions. when using the provided dispatch.
export default function createMiddleware() {
return ({dispatch, getState}) => next => (action) => {
if(action.type !== 'FOO') {
return next(action);
}
dispatch(thunkActionHere); // this is the issue
}
}
I would like to dispatch this thunk action back to the beginning of the middleware chain so that redux-thunk can handle it. Is this possible?
update:
function createMiddleware(extraArgument) {
return function ({dispatch, getState}) {
return function (next) {
return function (action) {
switch (action.type) {
case 'FOO1':
dispatch({type: 'NORMAL_ACTION'}); // works fine
break;
case 'FOO2':
dispatch(function() {
return (dispatch, getState) => { // Error: Actions must be plain objects. Use custom middleware for async actions.
console.log('inside the thunk');
};
});
break;
default:
return next(action);
}
};
};
};
}
const middleware = createMiddleware();
middleware.withExtraArgument = createMiddleware;
export default middleware;
Here's my store configuration:
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState, compose(
// Add other middleware on this line...
applyMiddleware(bugsnagErrorCatcherMiddleware()),
applyMiddleware(thunk.withExtraArgument({APIFactory, PusherManager})),
applyMiddleware(webrtcVideoMiddleware.withExtraArgument(PusherManager)), // this is the middleware above
applyMiddleware(bugsnagbreadcrumbLoggerMiddleware()),
)
);
return store;
}
I cannot put my middleware before redux-thunk because then it doesn't receive actions that thunks dispatch.
Dispatching inside the middleware chain will send the action to the start of the middleware chain, and will call the thunk as usual (Demo - look at the console).
Why?
The original store.dispatch() (before applying middlewares) checks if the action is a plain POJO, and if not throws an error:
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
When you applyMiddleware() the dispatch is replaced by a new method, which is the chain of middleware, that call the original store.dispatch() in the end. You can see it in the applyMiddleware method:
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch // dispatch is now the original store's dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action) // this refers to the dispatch variable. However, it's not the original dispatch, but the one that was created by compose
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch) // dispatch is a composition of the chain, with the original dispatch in the end
return {
...store,
dispatch
}
}
}
btw - change your middleware to this, since the 1st function will prevent your middleware from working.
export default const createMiddleware = ({dispatch, getState}) => next => (action) => {
if(action.type !== 'FOO') {
return next(action);
}
dispatch(thunkActionHere); // this is the issue
}
It turns out the issue was in my store configuration. Using redux's compose caused the issue.
before:
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../redux/reducers';
import webrtcVideoMiddleware from '../redux/middleware/webrtcVideo';
import bugsnagErrorCatcherMiddleware from '../redux/middleware/bugsnag/errorCatcher';
import bugsnagbreadcrumbLoggerMiddleware from '../redux/middleware/bugsnag/breadcrumbLogger';
import * as APIFactory from '../services/APIFactory';
import Pusher from '../services/PusherManager';
const PusherManager = new Pusher(false);
export default function configureStore(initialState) {
return createStore(rootReducer, initialState, compose(
applyMiddleware(bugsnagErrorCatcherMiddleware()),
applyMiddleware(thunk.withExtraArgument({APIFactory, PusherManager})),
applyMiddleware(webrtcVideoMiddleware(PusherManager)),
applyMiddleware(bugsnagbreadcrumbLoggerMiddleware())
));
}
after:
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../redux/reducers';
import webRTCVideoMiddleware from '../redux/middleware/webrtcVideo';
import bugsnagErrorCatcherMiddleware from '../redux/middleware/bugsnag/errorCatcher';
import bugsnagBreadcrumbLoggerMiddleware from '../redux/middleware/bugsnag/breadcrumbLogger';
import * as APIFactory from '../services/APIFactory';
import Pusher from '../services/PusherManager';
const PusherManager = new Pusher(false);
export default function configureStore(initialState) {
const middleware = [
bugsnagErrorCatcherMiddleware(),
thunk.withExtraArgument({APIFactory, PusherManager}),
webRTCVideoMiddleware.withExtraArgument(PusherManager),
bugsnagBreadcrumbLoggerMiddleware(),
];
return createStore(rootReducer, initialState, applyMiddleware(...middleware));
}
We use composeWithDevTools from redux-devtools-extension. Same issue and same solution as noted above. Just needed to move to using applyMiddleware(...middlewares) instead of multiple applyMiddleware(middleware), applyMiddleware(middleware) as arguments to the composition.
I'm trying to develop a NodeJS app connecting to Firebase. I can connect successfully, but I'm unable to figure how to manage the scope in the then call.
I'm using NodeJS 6.9.2
My test implementation looks like this:
const EventEmitter = require('events');
const fb = require('firebase')
class FireGateway extends EventEmitter {
constructor() {
super();
if ( this.instance ) {
return this.instance;
}
// INIT
var fbConfig = {
apiKey: "xxxxx",
authDomain: "xxxxx.firebaseapp.com",
databaseURL: "https://xxxxx.firebaseio.com/"
};
fb.initializeApp(fbConfig)
this.instance = this;
this.testvar = "aaa";
}
login() {
fb.auth().signInWithEmailAndPassword ("email", "pwd")
.catch(function(error) {
// Handle Errors here.
}).then( function(onresolve, onreject) {
if (onresolve) {
console.log(this.testvar);
// "Cannot read property 'testvar' of undefined"
this.emit('loggedin');
// error as well
}
})
}
}
module.exports = FireGateway;
------
...
var FireGateway = require('./app/fireGateway');
this.fireGW = new FireGateway();
this.fireGW.login();
....
Any idea how can I manage it?
The callback passed to then is being called asynchronously from another context, so the this doesn't correspond to the instantiated object.
Using ES6 arrow functions you can keep your object context, since an arrow function does not create its own this context.
By the way, the syntax you are using in the then method is not correct, then accepts two callbacks with one argument each one. Check the syntax here.
The catch before the then is not necessary as well I think, it would make more sense to put it at the end.
It would be something like this:
login() {
fb.auth().signInWithEmailAndPassword("email", "pwd")
.then(
(onResolve) => {
console.log(this.testvar);
this.emit('loggedin');
},
(onReject) = > {
// error handling goes here
});
}
On the other hand, it seems login method is doing an asynchronous operation, so you might want to wait for it to finish in your code. I would make the login method return a Promise, so you can wait for it outside:
login() {
return fb.auth().signInWithEmailAndPassword("email", "pwd")
...
}