How to generate unit tests for an existing Angular2 application with Jasmine Karma

I joined the team developing the Angular2 application, which needs all the unit tests that need to be done using the Jasmine system. I was wondering if there is a tool capable of generating specification files for each class (type of boiler plate code), placing test cases based on available methods and / or based on attributes such as * ng-If in the templates. Below is an example of the a.component.js component

import { Component, Input, Output, Inject, OnChanges, EventEmitter, OnInit } from '@angular/core'; import {Http} from '@angular/http'; @Component({ selector: 'a-component', template : ` <div *ng-If="model"> <a-child-component [model]="model"> </a-child-component> </div>` }) export class AComponent implements OnInit { @Input() anInput; ngOnInit() { if(this.anInput){ this.model = anInput; } } constructor(@Inject(Http) http){ this.restAPI = http; } methodOne(arg1,arg2){ //do something } methodTwo(arg1,arg2){ //do something } //... } 

And generates a specification file: a.componenet.spec.js

 import { beforeEach,beforeEachProviders,describe,expect,it,injectAsync } from 'angular2/testing'; import { setBaseTestProviders } from 'angular2/testing'; import { TEST_BROWSER_PLATFORM_PROVIDERS,TEST_BROWSER_APPLICATION_PROVIDERS } from 'angular2/platform/testing/browser'; setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS); import { Component, Input, Output, Inject, OnChanges, EventEmitter, OnInit } from '@angular/core'; import { ComponentFixture, TestBed, inject } from '@angular/core/testing'; import { MockComponent } from 'ng2-mock-component'; import { async } from '@angular/core/testing'; import { Http } from '@angular/http'; import { HttpMock } from '../mocks/http.mock'; import { AComponent } from './a.component'; let model = {"propOne":[],"propTwo":"valueTwo"}; describe('AComponent', () => { let fixture; beforeEach(() => { TestBed.configureTestingModule({ declarations: [ AComponent, MockComponent({ selector: 'a-child-component', template:'Hello Dad!' ,inputs: ['model'] }) ], providers: [{ provide: Http, useClass: HttpMock }] }); fixture = TestBed.createComponent(AComponent); fixture.componentInstance.anInput= model; }); it('should create the component',() => { // }); it('should test methodOne',() => { // }); it('should test methodTwo',() => { // }); it('should generate the child component when model is populated',() => { // }); ) 
+6
source share
3 answers

It has been some time since I posted this question. I developed a visual code extension to help with this task, which I want to share with you. The point of this extension is not only to create a specification file, but also to create boiler room code for all the test cases you need to write. It also creates the mocks and injections you need to speed you up. he adds a test case that will fail if you have not completed all the tests. Feel free to remove it if it does not meet your needs. This was done for the Angular2 ES6 project, but you can upgrade it for typescript as you wish:

// description: This extension will create a specification file for the given js file. // if the js file is an Angular2 component, it then looks for the html template and creates a specification file containing the Mock componentenet class for each child element included in html

 var vscode = require('vscode'); var fs = require("fs"); var path = require("path"); // this method is called when your extension is activated // your extension is activated the very first time the command is executed function activate(context) { var disposable = vscode.commands.registerCommand('extension.unitTestMe', function () { // The code you place here will be executed every time your command is executed var htmlTags = ['h1','h2','h3','h4','h5','a','abbr','acronym','address','applet','area','article','aside','audio','b','base','basefont','bdi','bdo','bgsound','big','blink','blockquote','body','br','button','canvas','caption','center','cite','code','col','colgroup','command','content','data','datalist','dd','del','details','dfn','dialog','dir','div','dl','dt','element','em','embed','fieldset','figcaption','figure','font','footer','form','frame','frameset','head','header','hgroup','hr','html','i','iframe','image','img','input','ins','isindex','kbd','keygen','label','legend','li','link','listing','main','map','mark','marquee','menu','menuitem','meta','meter','multicol','nav','nobr','noembed','noframes','noscript','object','ol','optgroup','option','output','p','param','picture','plaintext','pre','progress','q','rp','rt','rtc','ruby','s','samp','script','section','select','shadow','slot','small','source','spacer','span','strike','strong','style','sub','summary','sup','table','tbody','td','template','textarea','tfoot','th','thead','time','title','tr','track','tt','u','ul','var','video','wbr']; var filePath; var fileName; if(vscode.window.activeTextEditor){ filePath = vscode.window.activeTextEditor.document.fileName; fileName = path.basename(filePath); if(fileName.lastIndexOf('.spec.') > -1 || fileName.lastIndexOf('.js') === -1 || fileName.substring(fileName.lastIndexOf('.js'),fileName.length) !== '.js'){ vscode.window.showErrorMessage('Please call this extension on a Javascript file'); }else{ var splitedName = fileName.split('.'); splitedName.pop(); var capitalizedNames = []; splitedName.forEach(e => { capitalizedNames.push(e.replace(e[0],e[0].toUpperCase())); }); var className = capitalizedNames.join(''); // ask for filename // var inputOptions = { // prompt: "Please enter the name of the class you want to create a unit test for", // value: className // }; // vscode.window.showInputBox(inputOptions).then(className => { let pathToTemplate; let worspacePath = vscode.workspace.rootPath; let fileContents = fs.readFileSync(filePath); let importFilePath = filePath.substring(filePath.lastIndexOf('\\')+1,filePath.lastIndexOf('.js')); let fileContentString = fileContents.toString(); let currentFileLevel = (filePath.substring(worspacePath.length,filePath.lenght).match(new RegExp("\\\\", "g")) || []).length; let htmlFile; if(fileContentString.indexOf('@Component({') > 0){ pathToTemplate = worspacePath + "\\unit-test-templates\\component.txt"; htmlFile = filePath.replace('.js','.html'); }else if(fileContentString.indexOf('@Injectable()') > 0){ pathToTemplate = worspacePath + "\\unit-test-templates\\injectableObject.txt"; } let fileTemplatebits = fs.readFileSync(pathToTemplate); let fileTemplate = fileTemplatebits.toString(); let level0,level1; switch(currentFileLevel){ case 1: level0 = '.'; level1 = './client'; break; case 2: level0 = '..'; level1 = '.'; break; case 3: level0 = '../..'; level1 = '..'; break; } fileTemplate = fileTemplate.replace(/(ComponentName)/g,className).replace(/(pathtocomponent)/g,importFilePath); //fileTemplate = fileTemplate.replace(/(pathtocomponent)/g,importFilePath); //let templateFile = path.join(templatesManager.getTemplatesDir(), path.basename(filePath)); let templateFile = filePath.replace('.js','.spec.js'); if(htmlFile){ let htmlTemplatebits = fs.readFileSync(htmlFile); let htmlTemplate = htmlTemplatebits.toString(); let componentsUsed = htmlTemplate.match(/(<[a-z0-9]+)(-[az]+){0,4}/g) || [];//This will retrieve the list of html tags in the html template of the component. let inputs = htmlTemplate.match(/\[([a-zA-Z0-9]+)\]/g) || [];//This will retrieve the list of Input() variables of child Components for(var q=0;q<inputs.length;q++){ inputs[q] = inputs[q].substring(1,inputs[q].length -1); } if(componentsUsed && componentsUsed.length){ for(var k=0;k<componentsUsed.length;k++){ componentsUsed[k] = componentsUsed[k].replace('<',''); } componentsUsed = componentsUsed.filter(e => htmlTags.indexOf(e) == -1); if(componentsUsed.length){ componentsUsed = componentsUsed.filter((item, pos,self) =>{ return self.indexOf(item) == pos;//remove duplicate }); let MockNames = []; componentsUsed.forEach(e => { var splitedTagNames = e.split('-'); if(splitedTagNames && splitedTagNames.length > 1){ var capitalizedTagNames = []; splitedTagNames.forEach(f => { capitalizedTagNames.push(f.replace(f[0],f[0].toUpperCase())); }); MockNames.push('Mock' + capitalizedTagNames.join('')); }else{ MockNames.push('Mock' + e.replace(e[0],e[0].toUpperCase())); } }) let MockDeclarationTemplatebits = fs.readFileSync(worspacePath + "\\unit-test-templates\\mockInportTemplace.txt"); let MockDeclarationTemplate = MockDeclarationTemplatebits.toString(); let inputList = ''; if(inputs && inputs.length){ inputs = inputs.filter(put => put !== 'hidden'); inputs = inputs.filter((item, pos,self) =>{ return self.indexOf(item) == pos;//remove duplicate }); inputs.forEach(put =>{ inputList += '@Input() ' + put + ';\r\n\t' }); } let declarations = ''; for(var i=0;i < componentsUsed.length; i++){ if(i != 0){ declarations += '\r\n'; } declarations += MockDeclarationTemplate.replace('SELECTORPLACEHOLDER',componentsUsed[i]).replace('MOCKNAMEPLACEHOLDER',MockNames[i]).replace('HTMLTEMPLATEPLACEHOLDER',MockNames[i]).replace('ALLINPUTSPLACEHOLDER',inputList); } fileTemplate = fileTemplate.replace('MockComponentsPlaceHolder',declarations); fileTemplate = fileTemplate.replace('ComponentsToImportPlaceHolder',MockNames.join(',')); }else{ fileTemplate = fileTemplate.replace('MockComponentsPlaceHolder',''); fileTemplate = fileTemplate.replace(',ComponentsToImportPlaceHolder',''); } }else{ fileTemplate = fileTemplate.replace('MockComponentsPlaceHolder',''); fileTemplate = fileTemplate.replace(',ComponentsToImportPlaceHolder',''); } }else{ fileTemplate = fileTemplate.replace('MockComponentsPlaceHolder',''); fileTemplate = fileTemplate.replace(',ComponentsToImportPlaceHolder',''); } fileTemplate = fileTemplate.replace(/(LEVEL0)/g,level0).replace(/(LEVEL1)/g,level1); if(fs.existsSync(templateFile)){ vscode.window.showErrorMessage('A spec file with the same name already exists. Please rename it or delete first.'); }else{ fs.writeFile(templateFile, fileTemplate, function (err) { if (err) { vscode.window.showErrorMessage(err.message); } else { vscode.window.showInformationMessage("The spec file has been created next to the current file"); } }); } } }else{ vscode.window.showErrorMessage('Please call this extension on a Javascript file'); } }); context.subscriptions.push(disposable); } exports.activate = activate; // this method is called when your extension is deactivated function deactivate() { } exports.deactivate = deactivate; 

To do this, you need 2 template files, one for components and one for injection services. You can add channels and other types of TS classes

template.txt:

 /** * Created by mxtano on 10/02/2017. */ import { beforeEach,beforeEachProviders,describe,expect,it,injectAsync } from 'angular2/testing'; import { setBaseTestProviders } from 'angular2/testing'; import { TEST_BROWSER_PLATFORM_PROVIDERS,TEST_BROWSER_APPLICATION_PROVIDERS } from 'angular2/platform/testing/browser'; setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS); import { Component, Input, Output, Inject, OnChanges, EventEmitter, OnInit } from '@angular/core'; import { ComponentFixture, TestBed, inject } from '@angular/core/testing'; import { async } from '@angular/core/testing'; import { YourService} from 'LEVEL1/service/your.service'; import { YourServiceMock } from 'LEVEL0/test-mock-class/your.service.mock'; import { ApiMockDataIfNeeded } from 'LEVEL0/test-mock-class/apiMockData'; import { FormBuilderMock } from 'LEVEL0/test-mock-class/form.builder.mock'; import { MockNoteEventController } from 'LEVEL0/test-mock-class/note.event.controller.mock'; import { ComponentName } from './pathtocomponent'; MockComponentsPlaceHolder describe('ComponentName', () => { let fixture; let ListOfFunctionsTested = []; beforeEach(() => { TestBed.configureTestingModule({ declarations: [ ComponentName ,ComponentsToImportPlaceHolder ], providers: [ //Use the appropriate class to be injected //{provide: YourService, useClass: YourServiceMock} ] }); fixture = TestBed.createComponent(ComponentName); //Insert initialising variables here if any (such as as link or model...) }); //This following test will generate in the console a unit test for each function of this class except for constructor() and ngOnInit() //Run this test only to generate the cases to be tested. it('should list all methods', async( () => { //console.log(fixture.componentInstance); let array = Object.getOwnPropertyNames(fixture.componentInstance.__proto__); let STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; let ARGUMENT_NAMES = /([^\s,]+)/g; array.forEach(item => { if(typeof(fixture.componentInstance.__proto__[item]) === 'function' && item !== 'constructor' && item !== 'ngOnInit'){ var fnStr = fixture.componentInstance.__proto__[item].toString().replace(STRIP_COMMENTS, ''); var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); if(result === null) result = []; var fn_arguments = "'"+result.toString().replace(/,/g,"','")+"'"; console.log("it('Should test "+item+"',()=>{\r\n\tListOfFunctionsTested.push('"+item+"');\r\n\t//expect(fixture.componentInstance."+item+"("+fn_arguments+")).toBe('SomeValue');\r\n});"); } }); expect(1).toBe(1); })); //This test will make sure that all methods of this class have at leaset one test case it('Should make sure we tested all methods of this class',() =>{ let fn_array = Object.getOwnPropertyNames(fixture.componentInstance.__proto__); fn_array.forEach(fn=>{ if(typeof(fixture.componentInstance.__proto__[fn]) === 'function' && fn !== 'constructor' && fn !== 'ngOnInit'){ if(ListOfFunctionsTested.indexOf(fn)=== -1){ //this test will fail but will display which method is missing on the test cases. expect(fn).toBe('part of the tests. Please add ',fn,' to your tests'); } } }); }) }); 

Here is the template for Mock Components referenced by the mockInportTemplace.txt extension:

 @Component({ selector: 'SELECTORPLACEHOLDER', template: 'HTMLTEMPLATEPLACEHOLDER' }) export class MOCKNAMEPLACEHOLDER { //Add @Input() variables here if necessary ALLINPUTSPLACEHOLDER } 

Here is the template referenced by the extension for injection:

 import { beforeEach,beforeEachProviders,describe,expect,it,injectAsync } from 'angular2/testing'; import { setBaseTestProviders } from 'angular2/testing'; import { TEST_BROWSER_PLATFORM_PROVIDERS,TEST_BROWSER_APPLICATION_PROVIDERS } from 'angular2/platform/testing/browser'; setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS); import { Component, Input, Output, Inject, OnChanges, EventEmitter, OnInit } from '@angular/core'; import { ComponentFixture, TestBed, inject } from '@angular/core/testing'; import { async } from '@angular/core/testing'; import { RestAPIMock } from 'LEVEL0/test-mock-class/rest.factory.mock'; import {Http} from '@angular/http'; //import { Subject } from 'rxjs/Subject'; import { ComponentName } from './pathtocomponent'; import { ApiMockData } from 'LEVEL0/test-mock-class/ApiMockData'; describe('ComponentName', () => { let objInstance; let service; let backend; let ListOfFunctionsTested = []; let singleResponse = { "properties": {"id": 16, "partyTypeId": 2, "doNotContact": false, "doNotContactReasonId": null, "salutationId": 1}}; let restResponse = [singleResponse]; beforeEach(() => { TestBed.configureTestingModule({ providers: [ ComponentName //Here you declare and replace an injected class by its mock object //,{ provide: Http, useClass: RestAPIMock } ] }); }); beforeEach(inject([ComponentName //Here you can add the name of the class that your object receives as Injection // , InjectedClass ], (objInstanceParam // , injectedObject ) => { objInstance = objInstanceParam; //objInstance.injectedStuff = injectedObject; })); it('should generate test cases for all methods available', () => { let array = Object.getOwnPropertyNames(objInstance.__proto__); let STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; let ARGUMENT_NAMES = /([^\s,]+)/g; array.forEach(item => { if(typeof(objInstance.__proto__[item]) === 'function' && item !== 'constructor' && item !== 'ngOnInit'){ var fnStr = objInstance.__proto__[item].toString().replace(STRIP_COMMENTS, ''); var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); if(result === null) result = []; var fn_arguments = "'"+result.toString().replace(/,/g,"','")+"'"; console.log("it('Should test "+item+"',()=>{\r\n\tListOfFunctionsTested.push('"+item+"');\r\n\t//expect(objInstance."+item+"("+fn_arguments+")).toBe('SomeValue');\r\n});"); } }); expect(1).toBe(1); }); //This test will make sure that all methods of this class have at leaset one test case it('Should make sure we tested all methods of this class',() =>{ let fn_array = Object.getOwnPropertyNames(objInstance.__proto__); fn_array.forEach(fn=>{ if(typeof(objInstance.__proto__[fn]) === 'function' && fn !== 'constructor' && fn !== 'ngOnInit'){ if(ListOfFunctionsTested.indexOf(fn)=== -1){ //this test will fail but will display which method is missing on the test cases. expect(fn).toBe('part of the tests. Please add ',fn,' to your tests'); } } }); }) }); 

The three files above should live inside your project under src in the folder referenced as unit template tags

Once you create this extension in your visual code, go to the JS file that you want to generate unit test, press F1 and enter UniteTestMe. make sure the specification file is not already created.

+2
source

There is a paid visual extension of the studio called Simon test, follow the link below https://marketplace.visualstudio.com/items?itemName=SimonTest.simontest This makes it possible to generate unit testing with the boiler plate code. The tracking period for this extension is 30 days.

0
source

I tested ngx-spec using Jasmine / Karma and it works:

Angular CLI creates .spec files for existing components


I also checked ngentest with jasmine / karma and it didn't work for me

Stack overflow question:

error TS1127: invalid character when starting karma tests in Angular 7

Github release

https://github.com/allenhwkim/ngentest/issues/17

0
source

Source: https://habr.com/ru/post/1264479/


All Articles