Last active
February 24, 2021 13:03
-
-
Save johnmcase/7b0c8544f56c230ceb77cbd244909a07 to your computer and use it in GitHub Desktop.
Jasmine to Jest: Adapted from https://dev.to/dylanwatsonsoftware/make-your-angular-tests-1000-faster-by-switching-from-karma-to-jest-1n33
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var glob = require("glob") | |
Reset = "\x1b[0m" | |
FgRed = "\x1b[31m" | |
FgGreen = "\x1b[32m" | |
FgYellow = "\x1b[33m" | |
FgWhite = "\x1b[37m" | |
let specs = glob.sync("src/**/*.spec.ts"); | |
let tests = glob.sync("src/**/*.test.ts"); | |
console.log(FgYellow, `${specs.join('\n')}`, Reset) | |
if (specs.length) { | |
console.log(FgRed, specs.length + " slow karma tests") | |
} else { | |
console.log(FgGreen, 'Wooooooooooooooooooooo! Jest conversion complete!') | |
} | |
console.log(FgWhite, tests.length + " fast jest tests") | |
console.log(FgGreen, (tests.length * 100 / (tests.length + specs.length)).toFixed(2) + "% complete in switching tests to jest", Reset) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var fs = require('fs') | |
var filename = process.argv[2] | |
if (!filename) { | |
let specs = require('glob').sync("src/**/*.spec.ts"); | |
for (spec of specs) { | |
if (!spec.includes('pact')) { | |
convertToJest(spec); | |
} | |
} | |
} else { | |
convertToJest(filename); | |
} | |
function convertToJest(filename) { | |
if (!filename.startsWith('C:')) { | |
filename = './' + filename | |
} | |
fs.readFile(filename, 'utf8', function (err, data) { | |
if (err) { | |
return console.log(err); | |
} | |
var result = data; | |
// result = result.replace(' } from \'@ngneat/spectator\';', ', SpyObject } from \'@ngneat/spectator/jest\';'); | |
// result = result.replace('} from \'@ngneat/spectator\';', ', SpyObject } from \'@ngneat/spectator/jest\';'); | |
result = result.replace(/\.and\.returnValue/g, '.mockReturnValue'); | |
result = result.replace(/\.spec\'/g, '.test'); | |
// result = result.replace(/jasmine\.SpyObj/g, 'SpyObj'); | |
result = result.replace(/spyOn/g, 'jest.spyOn'); | |
result = result.replace(/spyOnProperty/g, 'spyOn'); | |
result = result.replace(/expect\((.*)\.calls\.first\(\)\.args\)\.toEqual\(\[(.*)\]\);/g, 'expect($1).toHaveBeenCalledWith($2);') | |
result = result.replace(/expect\((.*)\.calls\.any\(\)\)\.toBe\((.*)\);/g, 'expect($1).toHaveBeenCalledWith($2);'); | |
result = result.replace(/expect\((.*)\.calls\.mostRecent\(\)(\.args\[.*\])?\)\.toEqual\((.*)\);/g, 'expect($1).toHaveBeenCalledWith($2);'); | |
result = result.replace(/expect\((.*)\.calls\.count\(\)\)\.toBe\((.*)\);/g, 'expect($1).toHaveBeenCalledTimes($2);'); | |
result = result.replace(/expect\((.*)\.calls\.count\(\)\)\.toEqual\((.*)\);/g, 'expect($1).toHaveBeenCalledTimes($2);'); | |
result = result.replace(/\.calls\.first\(\).args\[(\d+)\]/g, '.mock.calls[0][$1]'); | |
result = result.replace(/\.calls\.first\(\).args/g, '.mock.calls[0].args'); | |
result = result.replace(/.and.callFake/g, '.mockImplementation'); | |
result = result.replace(/.and.callThrough\(\);/g, ';'); | |
result = result.replace(/(.*).calls\.reset\(\);/g, '$1.mockClear();'); | |
result = result.replace(/jasmine.createSpyObj\(/g, 'createSpyObj('); | |
result = result.replace(/jasmine.createSpy\(\'(.*)\'\);/g, 'jest.fn();'); | |
// result = result.replace(/createService\(/g, 'createServiceFactory('); | |
// result = result.replace(/createService,/g, 'createServiceFactory,'); | |
if (result.includes('createSpyObj')) { | |
result = 'import { createSpyObj } from \'src/testing/createSpy\';\r\n' + result; | |
} | |
if (result.includes('jasmine.clock().mockDate')) { | |
result = 'import { advanceBy, advanceTo, clear } from \'jest-date-mock\';\r\n' + result; | |
} | |
// result = result.replace('import SpyObj = SpyObj;', ''); | |
// result = result.replace('import Spy = jasmine.Spy;', ''); | |
// result = result.replace('import createSpyObj = createSpyObj;', ''); | |
result = result.replace('import { configureTestSuite } from \'ng-bullet\';', 'import { configureTestSuite } from \'src/testing/jest-bullet\';'); | |
// result = result.replace(/ Spy;/g, ' jest.SpyInstance;'); | |
result = result.replace(/: jasmine.SpyObj</g, ': jest.Mocked<'); | |
result = result.replace(/: jasmine\.Spy</g, ': jest.MockedFunction<'); | |
result = result.replace(/: jasmine\.Spy/g, ': jest.Mock'); | |
// result = result.replace(/configureTestSuite\(\(\)\s*=\>\s*\{(.*?)\}\);/s, 'beforeEach(async(() => {$1}));'); | |
result = result.replace(/(\s)it\(\'/g, '$1test(\''); | |
result = result.replace(/(\s)it\(\`/g, '$1test(\`'); | |
result = result.replace(/.innerText/g, '.textContent.trim()'); | |
result = result.replace(/jasmine.clock\(\).install\(\)/g, 'jest.useFakeTimers()'); | |
result = result.replace(/jasmine.clock\(\).uninstall\(\)/g, 'jest.useRealTimers()'); | |
result = result.replace(/jasmine.clock\(\).mockDate\((.*)\)/g, 'advanceTo($1)'); | |
// if (!result.includes('@ngneat/spectator') && result.includes('SpyObject')) { | |
// result = 'import { SpyObject } from \'@ngneat/spectator/jest\';\r\n' + result; | |
// } | |
// if (result.includes('MatDialog') && !result.includes('@angular/material/dialog')) { | |
// result = result.replace(/import \{(.*)MatDialog, (.*)\}/g, 'import {$1$2}'); | |
// result = result.replace(/import \{(.*)MatDialogModule, (.*)\}/g, 'import {$1$2}'); | |
// result = result.replace(/import \{(.*)MatDialogModule(.*)\}/g, 'import {$1$2}'); | |
// result = result.replace(/import \{(.*)MAT_DIALOG_DATA, (.*)\}/g, 'import {$1$2}'); | |
// result = result.replace(/import \{(.*)MatDialogRef, (.*)\}/g, 'import {$1$2}'); | |
// result = 'import { MatDialog, MatDialogModule, MAT_DIALOG_DATA, MatDialogRef } from \'@angular/material/dialog\';\r\n' + result; | |
// } | |
// if (result.includes('withArgs')) { | |
// result = result.replace(/(.*)\.withArgs\((.*)\)\.mockReturnValue\((.*)\)/g, `$1.mockImplementation(flag => { | |
// switch (flag) { | |
// case $2: | |
// return $3; | |
// } | |
// })`); | |
// } | |
result = result.replace(/jest\.jest/g, 'jest'); | |
let newFile = filename.replace('.spec.ts', '.test.ts'); | |
fs.writeFile(newFile, result, 'utf8', function (err) { | |
if (err) | |
return console.log(err); | |
console.log('Successfully wrote ' + newFile); | |
if (newFile != filename) { | |
fs.unlinkSync(filename); | |
} | |
}); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const createSpyObj = <T = any>(_baseName, methodNames?): jest.Mocked<T> => { | |
const obj: any = {}; | |
for (let i = 0; i < methodNames.length; i++) { | |
obj[methodNames[i]] = jest.fn(); | |
} | |
return obj; | |
}; | |
export const createSpy = (_baseName?) => { | |
return jest.fn(); | |
}; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This code has been adapted from ng-bullet and modified to work with Jest | |
// https://www.npmjs.com/package/ng-bullet | |
import { Type } from '@angular/core'; | |
import { ComponentFixture, getTestBed, TestBed } from '@angular/core/testing'; | |
/** | |
* Reconfigures current test suit to prevent angular components re-compilation after every test run. | |
* Forces angular test bed to re-create zone and all injectable services by directly | |
* setting _instantiated variable to false after every test run. | |
* Cleanups all the changes and reverts test bed configuration after suite has finished. | |
* | |
* @param configureAction an optional delegate which can be used to configure test bed for the current test suite | |
* directly in the configureTestSuite call (you don't need extra BeforeAll in this case) | |
*/ | |
export const configureTestSuite = (configureAction?: () => void) => { | |
const testBedApi: any = getTestBed(); | |
const originReset = TestBed.resetTestingModule; | |
beforeAll(() => { | |
TestBed.resetTestingModule(); | |
TestBed.resetTestingModule = () => TestBed; | |
}); | |
if (configureAction) { | |
beforeAll((done?: jest.DoneCallback) => (async () => { | |
configureAction(); | |
await TestBed.compileComponents(); | |
})().then(done ? done : () => {}).catch(done && done.fail ? done.fail : err => console.log(err))); | |
} | |
afterEach(() => { | |
testBedApi._activeFixtures.forEach((fixture: ComponentFixture<any>) => fixture.destroy()); | |
testBedApi._instantiated = false; | |
}); | |
afterAll(() => { | |
TestBed.resetTestingModule = originReset; | |
TestBed.resetTestingModule(); | |
}); | |
}; | |
/** | |
* A wrapper class around ComponentFixture, which provides useful accessros: | |
* component - to access component instance of current the fixture | |
* element - to access underlying native element of the current component | |
* detectChanges - to run change detections using current fixture | |
* resolve - to resolve a type using current fixture's injector | |
*/ | |
export class TestCtx<T> { | |
constructor(public fixture: ComponentFixture<T>) { } | |
public get component() { return this.fixture.componentInstance; } | |
public get element(): HTMLElement { return this.fixture.debugElement.nativeElement; } | |
public detectChanges() { this.fixture.detectChanges(); } | |
public resolve(component: Type<any>) { return this.fixture.debugElement.injector.get(component); } | |
} | |
/** | |
* Creates TestCtx instance for the Angular Component which is not initialized yet (no ngOnInit called) | |
* Use case: you can override Component's providers before hooks are called. | |
* | |
* @param component - type of component to create instance of | |
* **/ | |
export const createTestContext = <T>(component: Type<T>) => { | |
const fixture = TestBed.createComponent<T>(component); | |
const testCtx = new TestCtx<T>(fixture); | |
return testCtx; | |
}; | |
/**Same as @function createTestContext, but waits till fixture becomes stable */ | |
export const createStableTestContext = async <T>(component: Type<T>) => { | |
const testCtx = createTestContext(component); | |
testCtx.detectChanges(); | |
await testCtx.fixture.whenStable(); | |
testCtx.detectChanges(); | |
return testCtx; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var preset = require("jest-preset-angular/jest-preset"); | |
module.exports = { | |
...preset, | |
preset: "jest-preset-angular", | |
setupFiles: ["jest-date-mock"], | |
setupFilesAfterEnv: [ | |
"<rootDir>/node_modules/jest-preset-angular/build/setupJest.js" | |
], | |
testMatch: ["**/*.test.ts"], | |
globals: { | |
...preset.globals, | |
"ts-jest": { | |
...preset.globals["ts-jest"], | |
tsConfig: "tsconfig.test.json", | |
isolatedModules: true | |
} | |
}, | |
transform: { | |
"^.+\\.(j|t)sx?$": "ts-jest", | |
}, | |
transformIgnorePatterns: [ | |
"<rootDir>/node_modules/(?!lodash-es/.*)" | |
], | |
coverageReporters: ['html', 'lcovonly', 'text-summary'], | |
coverageDirectory: '<rootDir>/coverage' | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'jest-preset-angular'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"extends": "./tsconfig.json", | |
"compilerOptions": { | |
"mapRoot": "./", | |
"outDir": "./out-tsc/spec", | |
"types": [ | |
"@jest/types", | |
"node" | |
], | |
"esModuleInterop": true, | |
"allowJs": true | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment