Szukaj…


Instalowanie środowiska testowego Jasmine

Najczęstszym sposobem testowania aplikacji Angular 2 jest platforma testowa Jasmine. Jasmine pozwala przetestować kod w przeglądarce.

zainstalować

Aby zacząć, wszystko czego potrzebujesz to jasmine-core pakiet (nie jasmine ).

npm install jasmine-core --save-dev --save-exact

Zweryfikować

Aby sprawdzić, czy Jasmine jest poprawnie skonfigurowana, utwórz plik ./src/unit-tests.html z następującą zawartością i otwórz go w przeglądarce.

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html;charset=utf-8">
  <title>Ng App Unit Tests</title>
  <link rel="stylesheet" href="../node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
  <script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
  <script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
  <script src="../node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
</head>
<body>
   <!-- Unit Testing Chapter #1: Proof of life.  -->
   <script>
     it('true is true', function () {
       expect(true).toEqual(true);
     });
   </script>
</body>
</html>

Konfigurowanie testów z Gulp, Webpack, Karma i Jasmine

Pierwszą rzeczą, jakiej potrzebujemy, jest poinformowanie karmy, aby korzystała z Webpacka do czytania naszych testów, w konfiguracji, którą ustawiliśmy dla silnika Webpacka. Tutaj używam babel, ponieważ piszę mój kod w ES6, możesz to zmienić dla innych smaków, takich jak Typescript. Albo używam szablonów Pug (wcześniej Jade), nie musisz.

Mimo to strategia pozostaje taka sama.

Oto konfiguracja webpacka:

const webpack = require("webpack");
let packConfig = {
    entry: {},
    output: {},
    plugins:[
        new webpack.DefinePlugin({
            ENVIRONMENT: JSON.stringify('test')
        })
    ],
    module: {
       loaders: [
        {
            test: /\.js$/,
            exclude:/(node_modules|bower_components)/,
            loader: "babel",
            query:{
                presets:["es2015", "angular2"]
            }
        },
        {
            test: /\.woff2?$|\.ttf$|\.eot$|\.svg$/,
            loader: "file"
        },
        {
            test: /\.scss$/,
            loaders: ["style", "css", "sass"]
        },
        {
            test: /\.pug$/,
            loader: 'pug-html-loader'
        },
        ]
    },
    devtool : 'inline-cheap-source-map'
};
module.exports = packConfig;

Następnie potrzebujemy pliku karma.config.js, aby użyć konfiguracji webpacka:

const packConfig = require("./webpack.config.js");
module.exports = function (config) {
    config.set({
    basePath: '',
    frameworks: ['jasmine'],
    exclude:[],
    files: [
        {pattern: './karma.shim.js', watched: false}
    ],

    preprocessors: {
        "./karma.shim.js":["webpack"]
    },
    webpack: packConfig,

    webpackServer: {noInfo: true},

    port: 9876,

    colors: true,

    logLevel: config.LOG_INFO,

    browsers: ['PhantomJS'],

    concurrency: Infinity,

    autoWatch: false,
    singleRun: true
});
};

Do tej pory powiedzieliśmy Karmie, aby korzystała z webpacka, i powiedzieliśmy, aby rozpoczynała się od pliku o nazwie karma.shim.js . ten plik będzie działał jako punkt wyjścia dla pakietu WWW. webpack odczyta ten plik i użyje importu i zażąda instrukcji, aby zebrać wszystkie nasze zależności i uruchomić nasze testy.

Spójrzmy teraz na plik karma.shim.js:

// Start of ES6 Specific stuff
import "es6-shim";
import "es6-promise";
import "reflect-metadata";
// End of ES6 Specific stuff

import "zone.js/dist/zone";
import "zone.js/dist/long-stack-trace-zone";
import "zone.js/dist/jasmine-patch";
import "zone.js/dist/async-test";
import "zone.js/dist/fake-async-test";
import "zone.js/dist/sync-test";
import "zone.js/dist/proxy-zone";

import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of';

Error.stackTraceLimit = Infinity;

import {TestBed} from "@angular/core/testing";
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting} from "@angular/platform-browser-dynamic/testing";

TestBed.initTestEnvironment(
     BrowserDynamicTestingModule,
     platformBrowserDynamicTesting());

let testContext = require.context('../src/app', true, /\.spec\.js/);
testContext.keys().forEach(testContext);

Zasadniczo importujemy TestBed z kątowych testów rdzenia i inicjujemy środowisko, ponieważ musi ono zostać zainicjowane tylko raz dla wszystkich naszych testów. Następnie przeglądamy katalog src / app rekurencyjnie i odczytujemy każdy plik, który kończy się na .spec.js i podajemy go do testContext, aby mogły zostać uruchomione.

Zazwyczaj próbuję postawić moje testy w tym samym miejscu co klasa. Personalny gust, ułatwia mi importowanie zależności i refaktoryzację klas. Ale jeśli chcesz umieścić swoje testy gdzie indziej, na przykład w katalogu src / test , masz szansę. zmień linię przed ostatnią w pliku karma.shim.js.

Doskonały. co zostało? ah, zadanie przełknięcia, które korzysta z pliku karma.config.js, który stworzyliśmy powyżej:

gulp.task("karmaTests",function(done){
    var Server = require("karma").Server;
    new Server({
        configFile : "./karma.config.js",
        singleRun: true,
        autoWatch: false
    }, function(result){
        return result ? done(new Error(`Karma failed with error code ${result}`)):done();
    }).start();
}); 

Teraz uruchamiam serwer z utworzonego przez nas pliku konfiguracyjnego, mówiąc mu, aby uruchomił się raz i nie czekał na zmiany. Uważam, że lepiej mi to odpowiada, ponieważ testy zostaną uruchomione tylko wtedy, gdy będę gotowy do ich uruchomienia, ale oczywiście, jeśli chcesz inaczej, wiesz, gdzie zmienić.

A jako mój ostatni przykładowy kod, oto zestaw testów do samouczka Angular 2 „Tour of Heroes”.

import {
    TestBed,
    ComponentFixture,
    async
} from "@angular/core/testing";

import {AppComponent} from "./app.component";
import {AppModule} from "./app.module";
import Hero from "./hero/hero";

describe("App Component", function () {

    beforeEach(()=> {
        TestBed.configureTestingModule({
            imports: [AppModule]
        });
    
        this.fixture = TestBed.createComponent(AppComponent);
        this.fixture.detectChanges();
    });

    it("Should have a title", async(()=> {
        this.fixture.whenStable().then(()=> {
            expect(this.fixture.componentInstance.title).toEqual("Tour of Heros");
        });
    }));

    it("Should have a hero", async(()=> {
        this.fixture.whenStable().then(()=> {
            expect(this.fixture.componentInstance.selectedHero).toBeNull();
        });
    }));

    it("Should have an array of heros", async(()=>
        this.fixture.whenStable().then(()=> {
            const cmp = this.fixture.componentInstance;
            expect(cmp.heroes).toBeDefined("component should have a list of heroes");
            expect(cmp.heroes.length).toEqual(10, "heroes list should have 10 members");
            cmp.heroes.map((h, i)=> {
                expect(h instanceof Hero).toBeTruthy(`member ${i} is not a Hero instance. ${h}`)
            });
        })));

        it("Should have one list item per hero", async(()=>
        this.fixture.whenStable().then(()=> {
            const ul = this.fixture.nativeElement.querySelector("ul.heroes");
            const li = Array.prototype.slice.call(
                this.fixture.nativeElement.querySelectorAll("ul.heroes>li"));
            const cmp = this.fixture.componentInstance;
            expect(ul).toBeTruthy("There should be an unnumbered list for heroes");
            expect(li.length).toEqual(cmp.heroes.length, "there should be one li for each hero");
            li.forEach((li, i)=> {
                expect(li.querySelector("span.badge"))
                    .toBeTruthy(`hero ${i} has to have a span for id`);
                expect(li.querySelector("span.badge").textContent.trim())
                    .toEqual(cmp.heroes[i].id.toString(), `hero ${i} had wrong id displayed`);
                expect(li.textContent)
                    .toMatch(cmp.heroes[i].name, `hero ${i} has wrong name displayed`);
            });
        })));

    it("should have correct styling of hero items", async(()=>
        this.fixture.whenStable().then(()=> {
            const hero = this.fixture.nativeElement.querySelector("ul.heroes>li");
            const win = hero.ownerDocument.defaultView ||hero.ownerDocument.parentWindow;
            const styles = win.getComputedStyle(hero);
            expect(styles["cursor"]).toEqual("pointer", "cursor should be pointer on hero");
            expect(styles["borderRadius"]).toEqual("4px", "borderRadius should be 4px");
        })));

    it("should have a click handler for hero items",async(()=>
        this.fixture.whenStable().then(()=>{
            const cmp = this.fixture.componentInstance;
            expect(cmp.onSelect)
                .toBeDefined("should have a click handler for heros");
            expect(this.fixture.nativeElement.querySelector("input.heroName"))
                .toBeNull("should not show the hero details when no hero has been selected");
            expect(this.fixture.nativeElement.querySelector("ul.heroes li.selected"))
                .toBeNull("Should not have any selected heroes at start");

            spyOn(cmp,"onSelect").and.callThrough();
            this.fixture.nativeElement.querySelectorAll("ul.heroes li")[5].click();

            expect(cmp.onSelect)
                .toHaveBeenCalledWith(cmp.heroes[5]);
            expect(cmp.selectedHero)
                .toEqual(cmp.heroes[5], "click on hero should change hero");
        })
    ));
});

Na uwagę zasługuje to, jak mamy beforeEach () konfiguruje moduł testowy i tworzy komponent w teście, a także jak wywołujemy funkcję replaceChanges () , aby kąt rzeczywiście przechodził podwójne wiązanie i wszystko.

Zauważ, że każdy test jest wywołaniem async () i zawsze czeka na spełnienie obietnicy whenStable przed sprawdzeniem urządzenia. Następnie ma dostęp do komponentu poprzez componentInstance i do elementu poprzez nativeElement .

Jest jeden test, który sprawdza poprawność stylizacji. w ramach samouczka zespół Angular demonstruje użycie stylów wewnątrz komponentów. W naszym teście używamy getComputedStyle (), aby sprawdzić, czy style pochodzą z miejsca, w którym podaliśmy, jednak potrzebujemy do tego obiektu Window, i otrzymujemy go z elementu, jak widać w teście.

Testowanie usługi HTTP

Zwykle usługi wywołują zdalny interfejs API w celu pobrania / wysłania danych. Ale testy jednostkowe nie powinny wykonywać połączeń sieciowych. Angular wewnętrznie używa klasy XHRBackend do wykonywania żądań HTTP. Użytkownik może to zmienić, aby zmienić zachowanie. Moduł testowania kątowego udostępnia MockBackend i MockConnection , których można używać do testowania i potwierdzania żądań HTTP.

posts.service.ts Ta usługa trafia do punktu końcowego interfejsu API, aby pobrać listę postów.

import { Http } from '@angular/http';
import { Injectable } from '@angular/core';
import { Observable }     from 'rxjs/rx';

import 'rxjs/add/operator/map';

export interface IPost {
    userId: number;
    id: number;
    title: string;
    body: string;
}

@Injectable()
export class PostsService {
    posts: IPost[];

    private postsUri = 'http://jsonplaceholder.typicode.com/posts';

    constructor(private http: Http) {
    }

    get(): Observable<IPost[]> {
        return this.http.get(this.postsUri)
                .map((response) => response.json());
    }
}

posts.service.spec.ts Tutaj przetestujemy powyższą usługę, wyśmiewając połączenia HTTP API.

import { TestBed, inject, fakeAsync } from '@angular/core/testing';
import {
    HttpModule,
    XHRBackend,
    ResponseOptions,
    Response,
    RequestMethod
} from '@angular/http';
import {
    MockBackend,
    MockConnection
} from '@angular/http/testing';

import { PostsService } from './posts.service';

describe('PostsService', () => {
    // Mock http response
    const mockResponse = [
        {
            'userId': 1,
            'id': 1,
            'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
            'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'
        },
        {
            'userId': 1,
            'id': 2,
            'title': 'qui est esse',
            'body': 'est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla'
        },
        {
            'userId': 1,
            'id': 3,
            'title': 'ea molestias quasi exercitationem repellat qui ipsa sit aut',
            'body': 'et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut'
        },
        {
            'userId': 1,
            'id': 4,
            'title': 'eum et est occaecati',
            'body': 'ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit'
        }
    ];

    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [HttpModule],
            providers: [
                {
                    provide: XHRBackend,
                    // This provides mocked XHR backend
                    useClass: MockBackend
                },
                PostsService
            ]
        });
    });

    it('should return posts retrieved from Api', fakeAsync(
        inject([XHRBackend, PostsService],
            (mockBackend, postsService) => {
                mockBackend.connections.subscribe(
                    (connection: MockConnection) => {
                        // Assert that service has requested correct url with expected method
                        expect(connection.request.method).toBe(RequestMethod.Get);
                        expect(connection.request.url).toBe('http://jsonplaceholder.typicode.com/posts');
                        // Send mock response
                        connection.mockRespond(new Response(new ResponseOptions({
                            body: mockResponse
                        })));
                    });

                postsService.get()
                    .subscribe((posts) => {
                        expect(posts).toBe(mockResponse);
                    });

            })));
});

Testowanie elementów kątowych - podstawowe

Kod komponentu podano poniżej.

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: '<h1>{{title}}</h1>'
})
export class MyAppComponent{
  title = 'welcome';
}

Do testowania kątowego, angular zapewnia narzędzia testowe wraz ze strukturą testową, która pomaga w pisaniu dobrego przypadku testowego w angular. Narzędzia kątowe można importować z @angular/core/testing

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyAppComponent } from './banner-inline.component';

describe('Tests for MyAppComponent', () => {
  
  let fixture: ComponentFixture<MyAppComponent>;
  let comp: MyAppComponent;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [
        MyAppComponent
      ]
    });
  });

  beforeEach(() => {

    fixture = TestBed.createComponent(MyAppComponent);
    comp = fixture.componentInstance;

  });

  it('should create the MyAppComponent', () => {
    
      expect(comp).toBeTruthy();  

  });

});

W powyższym przykładzie jest tylko jeden przypadek testowy, który wyjaśnia przypadek testowy na istnienie komponentu. W powyższym przykładzie TestBed narzędzi do testowania kątowego, takich jak TestBed i ComponentFixture .

TestBed służy do tworzenia modułu testowania kątowego, a my konfigurujemy ten moduł za pomocą metody configureTestingModule celu wytworzenia środowiska modułu dla klasy, którą chcemy przetestować. Moduł testowy do skonfigurowania przed wykonaniem każdego przypadku testowego, dlatego konfigurujemy moduł beforeEach funkcji beforeEach .

Metoda createComponent TestBed służy do tworzenia instancji testowanego komponentu. createComponent zwraca ComponentFixture . Urządzenie zapewnia dostęp do samej instancji komponentu.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow