Jest: How to access reporter instance from setupFilesAfterEnv script? - jestjs

I have my custom reporters
module.exports = {
...
reporters: [‘default’, ‘customReporter.js’]
...
}
And I have a setup script which I hook to setupFilesAfterEnv
module.exports = {
...
setupFilesAfterEnv: [‘setup.js’]
...
}
How can I access customReporter instance from setup.js?

I followed what allure-jest did in https://github.com/zaqqaz/jest-allure/blob/master/src/setup.ts.
I changed my setup.js code in a way that my reporter implements Jasmine Reporter interface instead of Jest Reporter, then register as reporter using jasmine.getEnv().addReporter().

Related

NestJs TypeOrm Custom Repository Unit Test Case

My nestjs project uses typeorm custom repositories.
#EntityRepository(MyEntity)
export class MyRepository extends AbstractRepository<MyEntity> {
public operation1(entity) {
/**
* Few lines here
*/
this.manager.save(entity);
}
}
I am trying to write unit testcases using jest for the method operation1, But I am not able to mock this.manager..
Is there a way to mock this.manager. methods?
If you are using Jest. Let's try this way:
let myRepository: Repository<MyRepository>;
jest
.spyOn(myRepository, 'manage')
.mockImplementation(() => Promise.resolve(entity);

Mock WebAPI interface using ts-mockito

I'm writing a unit test for a class which uses browser WebAPI interface.
I use ts-mockito to mock the interface (a WebGL2RenderingContext in my case).
When I run the test, Node throws ReferenceError: WebGL2RenderingContext is not defined
which is understandable, because the test is run under NodeJS environment, not browser, so the class/interface doesn't exist.
Is there any way to make NodeJS environment aware of the WebAPI interfaces, so that it's possible to be mocked?
NOTE: Since it's a unit test, it should NOT be run on a real browser.
jsdom seems to be a possible solution, but I have no idea how to mock it with ts-mockito.
The following snippet illustrate what I'm trying to do:
import { mock, instance, verify } from 'ts-mockito'
// ========== CLASS ==========
class DummyClass {
dummyMethod() : void {}
}
class TestedClass {
static testDummy(dummy : DummyClass) : void {
dummy.dummyMethod();
}
static testGlCtx(glCtx : WebGL2RenderingContext) : void {
glCtx.flush();
}
}
// ========== TEST ==========
describe('DummyClass', () => {
// This test passed successfully
it('works fine', () => {
const mockDummy = mock(DummyClass);
TestedClass.testDummy( instance(mockDummy) );
verify( mockDummy.dummyMethod() ).once();
});
});
describe('WebGL interface', () => {
it('works fine', () => {
// This line failed with 'ReferenceError: WebGL2RenderingContext is not defined'
const mockGLCtx = mock(WebGL2RenderingContext);
TestedClass.testGlCtx( instance(mockGLCtx) );
verify( mockGLCtx.flush() ).once();
});
});
Run using mocha with the command mocha --require ts-node/register 'test.ts'.
There are two solutions: For common DOM APIs, and for generic mocking.
For common DOM APIs
As detailed in this StackOverflow answer, jsdom can be used to bring DOM APIs into NodeJS runtime environment.
Run npm install --save-dev jsdom global-jsdom
and change Mocha's command to
mocha --require ts-node/register --require global-jsdom/register 'test.ts'
NOTE: global-jsdom is the newer & updated version of jsdom-global.
This solution works for common DOM APIs (such as HTMLElement, SVGElement, File),
but it doesn't work for more specialized APIs (WebGL, Crypto, audio & video).
For generic interface mocking
Turns out ts-mockito has a way to mock interfaces, including DOM & any browser Web APIs.
So the above test code can be changed to:
describe('WebGL interface', () => {
it('works fine', () => {
const mockGLCtx = mock<WebGL2RenderingContext>();
TestedClass.testGlCtx( instance(mockGLCtx) );
verify( mockGLCtx.flush() ).once();
});
});
and the test will run successfully.

How to use TypeScript in a Custom Test Environment file in Jest?

I need to enable some global variables to be reachable for my test so I am setting up a Custom Environment to be used in the testEnvironment option in my jest.config.json to achieve that.
For our project we have a TypeScript file that we use for setupFilesAfterEnv option and that works just fine, however the testEnvironment seems to support only ES5. Is there any way to use TypeScript in such option?
I successfully created a Custom Jest Environment using ES5 syntax, however since we are injecting global variables I need TypeScript to also declare a global namespace see: https://stackoverflow.com/a/42304473/4655076.
{
///...
setupFilesAfterEnv: ['<rootDir>/test/setup.ts'], // This works with ts
testEnvironment: '<rootDir>/test/customJestEnvironment.ts', // This doesn't work with ts
}
You might find this helpful: Configure Jest global tests setup with .ts file (TypeScript)
But basically you can only pass in compiled JS files as environments.
You can do what that article suggests. But it didn't work for me out of the box. So I manually compile my env.
i.e.
in package.json
"test": "tsc --lib es6 --target es6 --skipLibCheck -m commonjs --esModuleInterop true path/to/env.ts &&
jest --config=jest.config.js",
And in jest.config.js
{
testEnvironment: '<rootDir>/path/to/env.js', // Note JS extension.
}
I solved this by using ts-node and the following command:
node -r ts-node/register ./node_modules/jest/bin/jest.js
This essentially compiles the typescript on-the-fly, so that jest receives the emitted javascript, without the need of actually compiling your typescript sources to js.
You will need to enable esModuleInterop TS compiler option for this to work properly.
TestEnvironment.ts
import NodeEnvironment from 'jest-environment-node';
import type {Config} from '#jest/types';
class TestEnvironment extends NodeEnvironment {
constructor(config: Config.ProjectConfig) {
super(config);
// this.testPath = context.testPath;
// this.docblockPragmas = context.docblockPragmas;
}
public async setup(): Promise<void> {
await super.setup();
console.log('SETTING UP...');
// await someSetupTasks(this.testPath);
// this.global.someGlobalObject = createGlobalObject();
// // Will trigger if docblock contains #my-custom-pragma my-pragma-value
// if (this.docblockPragmas['my-custom-pragma'] === 'my-pragma-value') {
// // ...
// }
}
public async teardown(): Promise<void> {
await super.teardown();
console.log('TEARING DOWN!');
// this.global.someGlobalObject = destroyGlobalObject();
// await someTeardownTasks();
}
}
export default TestEnvironment;
This solution however, will break globalSetup -- if you use jest-ts.
As you might know, typescript files are just superset to javascript to provide strong type checking. Jest's engine/runtime however expects your files in CommonJS format javascript files.
You can have a separate tsconfig.env.json just for this env.ts. Compile this before running jest test and use the compiled env.js in your jest.config.js.
tsc -p tsconfig.env.json && jest
Also i have never seen people writing configuration files in TS.
why CommonJS ? because jest is essentially running on top of node. node supports Javascript files in CommonJS format. Node has started supporting es modules as well recently! This is a big thing!
You can create a global.d.ts file at the root of your project.
Then you can define global variables as seen below. In my case, it was a NestJS application, but you can define anything.
declare global {
namespace NodeJS {
interface Global {
app: INestApplication;
}
}
}
This is another example for client project where we define window properties like innerWidth;
declare namespace NodeJS {
interface Global {
innerWidth: number;
dispatchEvent: Function;
}
}
Inside your .d.ts definition file:
type MyGlobalFunctionType = (name: string) => void
Add members to the browser's window context:
interface Window {
myGlobalFunction: MyGlobalFunctionType
}
Same for NodeJS:
declare module NodeJS {
interface Global {
myGlobalFunction: MyGlobalFunctionType
}
}
Now you declare the root variable
declare const myGlobalFunction: MyGlobalFunctionType;
Then in a regular .ts file, but imported as side-effect, you actually implement it:
global/* or window */.myGlobalFunction = function (name: string) {
console.log("Hey !", name);
};
And finally use it elsewhere :
global/* or window */.myGlobalFunction("Ayush");
myGlobalFunction("Ayush");

Mock dependencies with Mocha and Typescript

I have a typescript project which uses mocha. Let's say we have two modules as follows.
// http.ts
export class Http {
}
// app.ts
import Http from './http';
export class App {
}
How could I mock the Http module when I'm testing the App?
The test is executed through the npm script as below.
"test": "cross-env NODE_ENV=test ./node_modules/mocha/bin/mocha",
And the mocha options (mocha.opts) looks like below.
test/setup.ts
--compilers ts:ts-node/register
--compilers tsx:ts-node/register
./src/**/*.spec.ts
ts-mock-imports gives you run time control over your imports and maintains type safety.
in app.spec.ts
import * as httpModule from 'http';
import { App } from '../src/app';
import { ImportMock } from 'ts-mock-imports';
const httpMock = ImportMock.mockClass(httpModule, 'Http');
const app = new App(); // App now uses a fake version of the Http class
Now you can control how the Http module behaves in your tests.
Mock the response to a get:
httpMock.mock('get', { data: true });
const response = app.makeGetRequest(); // returns { data: true }
The import statement in typescript is compiled to require. You can use proxyquire to mock any dependencies in your tests

How to generate multiple reports with mocha?

I want to have the following reports:
coverage
spec
xunit
all running in a single mocha execution from my grunt
Currently - I have to run the tests 3 times, each time to generate a different report(!).
So I use grunt-mocha-test with 2 configuration where only the reporter is different (once xunit-file and once spec).
And then I have grunt-mocha-istanbul that runs the tests yet again,and generates the coverage report.
I tried using
{
options: {
reporters : ['xunit-file', 'spec']
}
}
for grunt-mocha-test at least to bring it down to 2, but that doesn't work as well.
reading grunt-mocha-istanbul documentation, i can't seem to find any info about reporter configuration.
How can I resolve this?
Maybe this can help:
https://github.com/glenjamin/mocha-multi
AFAIK this is not supported in Mocha yet, but it is on its way:
https://github.com/mochajs/mocha/pull/1360
Hope this helps,
György
I ran into the same problem recently, and found nothing after looking around SO as well as GH issues. It seems that topic of officially supporting multiple reporters are getting postponed over and over.
Having said that having a custom solution is quite easy, assuming the reporters you want to combine already exist. What I did is to create a small and naive custom reporter, and used the reporter in .mocharc.js config.
// junit-spec-reporter.js
const mocha = require("mocha");
const JUnit = require("mocha-junit-reporter");
const Spec = mocha.reporters.Spec;
const Base = mocha.reporters.Base;
function JunitSpecReporter(runner, options) {
Base.call(this, runner, options);
this._junitReporter = new JUnit(runner, options);
this._specReporter = new Spec(runner, options);
return this;
}
JunitSpecReporter.prototype.__proto__ = Base.prototype;
module.exports = JunitSpecReporter;
// .mocharc.js
module.exports = {
reporter: './junit-spec-reporter.js',
reporterOptions: {
mochaFile: './tests-results/results.xml'
}
};
The example above shows how to use both spec and junit reporter.
More info on custom reporter: https://mochajs.org/api/tutorial-custom-reporter.html
Note that this is just a proof of concept and can be made prettier and more robust using more generic approach (and TypeScript).
Update 14.9.2021
I have created a utility package for this: https://www.npmjs.com/package/#netatwork/mocha-utils
For simultaneously reporting for spec and x-unit, there's also an NPM package called spec-xunit-file.
In grunt:
grunt.initConfig({
mochaTest: {
test: {
options: {
reporter: 'spec-xunit-file',
...
},
...
}
}
...
});

Resources