I am a bit confused while writing tests. My stack is mocha, chai and sinon + babel to transpile. And recently I've started to use ES6 imports and exports. It's working great so far, but I have trouble with mocking some dependencies. Here is my case:
service.js
import {v4} from 'uuid';
function doSomethingWithUuid() {
return v4();
}
export function doSomething() {
const newUuid = doSomethingWithUuid();
return newUuid;
}
service.test.js
import {doSomething} from './service';
describe('service', () => {
it('should doSomething' () => {
// how to test the return of doSomething ?
// I need to stub v4 but I don't know how...
});
});
Things I've considered: sinon.stub, but I have not managed to make it work. Trying to import all uuid with import * as uuid from 'uuid'. But within my service.js, it's still
the original function which is called...
Plus as imports are supposed to be read-only, once it'll be native, this solution wouldn't work...
The only interesting thing I found on the net was this solution, to add a function in my service in order to let the outside world to override my dependencies.
(see https://railsware.com/blog/2017/01/10/mocking-es6-module-import-without-dependency-injection/).
import * as originalUuid from 'uuid';
let {v4} = originalUuid;
export function mock(mockUuid) {
({v4} = mockUuid || originalUuid);
}
This is ok to write this little boilerplate code, but it troubles me to add it in my code... I would prefer to write boilerplate in my test or some config. Plus, I don't want to
have an IoC container, I want to keep my functions as little as possible and stay as fonctional as I can...
Do you have any ideas? :)
You should be able to use a module like proxyquire for this. This is not tested code, but it would go something like the following:
const proxyquire = require('proxyquire');
const uuidStub = { };
const service = proxyquire('./service', { uuid: uuidStub });
uuidStub.v4 = () => 'a4ead786-95a2-11e7-843f-28cfe94b0175';
describe('service', () => {
it('should doSomething' () => {
// doSomething() should now return the hard-coded UUID
// for predictable testing.
});
});
Related
I am developing plugin/index.ts file where I place async functions eg. clearing the database or uploading files to the app, but it starts to grow and I think about a way to keep it clean and structured.
In this video I see that there is a way to store functions in separate files and then export them using module.exports = { function } and then in the index.ts just import them using require.
But I can't have it working for my case.
This is a simplistic form of my plugins/index.ts file:
const uploadDocument = require('./documents');
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
on('task', {
clearDatabase: clearDatabase,
uploadDocument: uploadDocument,
});
async function clearDatabase() { ... }
}
I decided to move the code of function uploadDocument to the plugins/documents.ts file:
and this is how the file plugins/documents.ts looks like:
imports...
async function uploadDocument(fileName: string) { ... }
module.exports = { uploadDocument }
And when I run the test with a task this way:
cy.task("uploadDocument", 'Very_light_file.pdf')
I get this error in Cypress:
It looks like the problem is with your module.exports and the import.
Try this,
// document.ts
export async function uploadDocument(fileName: string) { ... }
// module.exports = { uploadDocument }
// plugins/index.ts
import { uploadDocument } from './documents'
From the post, I can tell TS is mixed JS there. The example is in JS, you're using TS, so there is no module.exports.
Try a pure JS version, then later convert it to TS.
If you look at cypress-io/cypress-realworld-app/blob/develop/cypress/plugins/index.ts you can see they change module.exports to:
// cypress/plugins/index.ts
export default (on, config) => {
config.env.defaultPassword = process.env.SEED_DEFAULT_USER_PASSWORD;
config.env.paginationPageSize = process.env.PAGINATION_PAGE_SIZE;
// Auth0
//
//
}
I am using Sinon with Ember.js Concurrency Tasks and am trying to stub the task in a test.
The code looks something like this:
component .ts file:
import Component from '#glimmer/component';
import { TaskGenerator, TaskInstance } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { taskFor } from 'ember-concurrency-ts';
export default class Container extends Component<Args> {
#task *myTask(): TaskGenerator<Data> {
const response: Data = yield json('someURL'); //json() returns a JSON object from a request to someURL
return response;
}
get task(): TaskInstance<Data> | null {
const task = taskFor(this.myTask);
return task.last ? task.last : task.perform();
}
#action
someMethod(): void {
const task = taskFor(this.myTask);
task.perform();
}
}
relevant test from component test file:
...
module('Integration | Component | container', function(hooks){
test('some test', async function(this: Context, assert) {
await render(hbs`
<Container #someMethod={{#someArgument}} as |c| >
// some code that uses c
</Container>
`);
}
How would I stub the myTask task? I would essentially like to have it so that I am able to manually control the response that comes out of myTask so an HTTP response doesn't have to be made in the test.
I would extend the component in your test file with your mocked task overriding the real one.
class TestContainer extends Container {
#task *myTask(): TaskGenerator<Data> {
return someMockData;
}
}
// ...
hooks.beforeEach(function() {
this.owner.register('component:container', TestContainer);
});
I'm not aware of any way to mock a single task in a component for testing. When the network is involved I reach for ember-cli-mirage which is built on pretender. Mirage is very good when working with ember-data models and can also be used to handle mocking any network request. If you're not using ember-data you may want to just use pretender or investigate the non-framework Mirage.js.
By mocking the network and returning canned data you will have the same control over your tests while testing the component as is. I really like this approach and have found it to be very reliable and stable for several years.
I do have tasks stubbing with sinon in my project. It's built a bit differently from the setup you have but perhaps you might get some inspiration.
So I have this task in my component
#(task(function* () {
yield this.exportxls.asXls.perform(someArg);
})) downloadXls;
and this asXls method is in the service
#(task(function* (mapping) {
// ...
}).drop()) asXls;
and then in my integration test I do the stub like this
this.owner.register('service:exportxls', Service.extend({
init() {
this._super(...arguments);
this.set('asXls', {
perform: sinon.stub()
});
}
}));
after that I can do usual checks
assert.ok(exportService.asXls.perform.calledOnce);
I assume this must be a pretty straightforward solution, but I am struggling to find a solution.
I have a function at the top of my tests:
jest.mock('../someFile', () => {
const setWidth = () => {
// lots of complex logic
};
return {
width: jest
.fn()
.mockImplementation(element => {
setWidth(element);
};
};
};
};
So, I understand that jest.mock is hoisted above the import statements in every test run, but say I would like to cut down on the boiler plate code I need in this file and as an example if setWidth was a really big function and I want to import it from another file, is there any way I could do this?
If I move setWidth to another file and try the following it fails due to the hoisting
import { setWidth } from ./setWidth
jest.mock('../someFile', () => {
return {
width: jest
.fn()
.mockImplementation(element => {
setWidth(element);
};
};
};
};
The error received is:
● Test suite failed to run
Invalid variable access: setWidth
Thanks in advance for any possible solutions!
jest.mock gets hoisted above the import, so that won't work. But what you can do is use requireActual
jest.mock('../someFile', () => {
const { setWidth } = jest.requireActual('./setWidth');
return {
width: jest
.fn()
.mockImplementation(element => {
setWidth(element);
};
};
};
};
Looks like you’re starting to go a bit crazy with "testing infrastructure" though - try to consider if there's a more "real" way (less testing infrastructure) you can test your code.
The most likely way to do this is to break the code your testing down into smaller functions/components.
I would like to add koa-validate to eggjs.
Code from koa-validate readme:
var koa = require('koa');
var app = koa();
var router = require('koa-router')();
require('koa-validate')(app);
app.use(require('koa-body')({multipart:true , formidable:{keepExtensions:true}}));
app.use(router.routes()).use(router.allowedMethods());
So I tried to add it as middleware as described in the eggjs docs:
// app/middleware/validate.js
const validate = require('koa-validate');
module.exports = (options, app) => {
validate(app);
return function session(ctx, next) {
return next();
}
}
But what I am actually looking for is to load the plugin 'on boot' and have the app object to add the validate plugin. Do you have any idea where I should place this?
thank you!
Okay, I solved it myself:
Add /app.js for life-cycle hooks and add the following code:
const validate = require('koa-validate');
class AppBootHook {
constructor(app) {
this.app = app;
validate(app);
}
}
module.exports = AppBootHook;
Instead of the documented this.checkQuery() the function is available as this.ctx.checkQuery.
Maybe this will help someone else.
Maybe not the answer but for egg js validating, there is already an official plugin there: https://github.com/eggjs/egg-validate
For the real answer: You can refer the example repo: https://github.com/Jeff-Tian/egg-useragent. This is a real world example that adds koa-useragent to eggjs framework. Core code is:
import {Application} from "egg"
import koaUserAgent from 'koa-useragent'
export default (app: Application) => {
app.use(koaUserAgent)
};
I'm on a project that uses jest as the testing framework for a nodejs server. On one controller (called ControllerImTesting), it news up an instance of a helper class (we'll call ClassIWantToMock and uses it for the controller. I already have many tests written around ClassIWantToMock itself so naturally when I test ControllerImTesting, I just want to mock ClassIWantToMock out.
It's pretty simple, I've created another js file in __mocks__ that contains a dumb version of ClassIWantToMock. When I new it up in ControllerImTesting, I want it to use the dumb version in __mocks__.
I've tried lots of configurations at this point but my desperation move is to use setMock like:
jest.setMock('/path/to/real/class/I/want/to/mock', '/path/to/dumb/version') How could this fail?
But when running the test, I get TypeError: ClassIWantToMock is not a constructor.
I look at my dumb version of ClassIWantToMock and change the style from something like const ClassIWantToMock = (req) => { to class ClassIWantToMock {. I get the same error which kind of makes sense because using the es6 class style is just syntactic sugar.
Just to confirm, in my real controller, I write a line, console.log(ClassIWantToMock) above the line where it news up the instance. It indeed prints out the '/path/to/dumb/version'. It is trying to mock it but cannot.
Is this a limitation of Jest? Or am I simply not using it correctly? --> How should this be done?
UPDATE
./ClassIWantToMock.js
class ClassIWantToMock {
constructor(stuff) {
this.stuff = stuff
}
doStuff() {
console.log('some real stuff')
}
}
module.exports = ClassIWantToMock
./__mocks__/ClassIWantToMock.js
class ClassIWantToMock {
constructor(fakeStuff) {
this.fakeStuff = fakeStuff
}
doStuff() {
console.log('some fake stuff')
}
}
module.exports = ClassIWantToMock
./ControllerImTesting.js
const ClassIWantToMock = require('./ClassIWantToMock')
class ControllerImTesting {
static aMethod(req, res, next) {
const helper = ClassIWantToMock('real stuff')
helper.doStuff()
return next()
}
}
module.exports = ClassIWantToMock
./ControllerImTesting.spec.js
jest.setMock('./ClassIWantToMock', './__mocks__/ClassIWantToMock')
const ControllerImTesting = require('./ControllerImTesting')
describe('basic test', () => {
test('should work', () => {
return ControllerImTesting.aMethod({}, {}, () => {}).then(() => {
// expect console to display 'some fake stuff'
})
})
})