2017-02-21 15 views
5

ジャスミンフレームワークですべての単体テストが必要なAngular2アプリを開発するチームに参加しました。 利用可能なメソッドに基づいてテストケースを配置したり、テンプレート内の* ng-Ifなどの属性に基づいて、各クラス(ボイラープレートコードの一種)の仕様ファイルを生成できるツールがあるかどうか疑問に思っていました。それは、I以来、しばらくしている ジャスミンカルマで既存のAngular2アプリの単体テストを生成する方法

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',() => { 
    // 
    }); 
) 

答えて

1

:ここ は、コンポーネントa.component.js

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 
    } 

    //... 
} 

の一例であり、specファイルを生成し、この質問を掲載しました。私はあなたと共有したいこのタスクを支援する視覚的なコード拡張を開発しました。 この拡張機能のポイントは、specファイルを作成するだけでなく、作成する必要のあるすべてのテストケース用のボイラープレートコードを生成します。 また、あなたはより速く進むために必要なモックと注射を作成します。 すべてのテストを実装していないと失敗するテストケースを追加します。それがあなたのニーズに合わない場合は、自由に取り外してください。 これはAngular2 ES6プロジェクトで行われましたが、希望通りにtypescript用に更新することができます:

//説明:この拡張機能は、指定されたjsファイル用のspecファイルを作成します。 // JSファイルがangular2 componenetであれば、それは、その後の作業には、このために

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]+)(-[a-z]+){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; 

HTMLに含まれてHTMLテンプレートを探し、それぞれの子のためのモックcomponenetクラスを含むspecファイルを作成します、あなたは2つのテンプレートファイル、1つはコンポーネント用、もう1つは注射可能なサービス用です。ここで

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

:ここ

/** 
* 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'); 
       } 
      } 
     }); 
    }) 

}); 

が延長mockInportTemplace.txtによって参照モックコンポーネントのテンプレートです:あなたは、パイプや

component.txtテンプレートTSクラスの他のタイプを追加することができます注射用拡張の参照テンプレート:

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'); 
       } 
      } 
     }); 
    }) 


}); 

thre eファイルは、srcの下にあるsrcの下にあるunit-test-templatesというフォルダにあります。

この拡張をビジュアルコードで作成したら、ユニットテストを生成するJSファイルに移動し、F1キーを押しますUniteTestMeと入力します。すでに作成されているスペックファイルがないことを確認してください。

+0

この拡張プロジェクトを書きましたか?私は試しましたが、失敗し、デバッグ情報なしのメッセージで停止します。 –

+0

私はこの拡張機能の開発をまだ進めていません。私は、あなたが拡張コードにステップインし、どこが失敗しているかを確認することをお勧めします。この記事を参照してください:https://code.visualstudio.com/docs/extensions/debugging-extensions – Mehdi

+0

問題が修正されました。私はTSファイルに問題がありましたが、今はサービスモック生成を追加したいと思っています。 –

関連する問題