.NET Core 2.2的Angular 7 - 全球天气(第3部分)

介绍

 在Global Weather part1Galobal Weather part2中,我们逐步 构建了Angular 7应用程序和.net核心2.2微API服务。在本文中,我们将开始研究单元测试。我将向您展示如何BDDfy在xUnit for .NET Core中使用。另外,我将向您展示如何为Angular创建和调试单元测试。

单元测试

进行自动化测试是确保软件应用程序完成其作者打算执行的操作的好方法。软件应用程序有多种类型的测试。这些包括集成测试,Web测试,负载测试等。单元测试测试单个软件组件和方法。单元测试应该只测试开发人员控制范围内的代码。他们不应该测试基础设施问题。基础设施问题包括数据库,文件系统和网络资源。

Test Driven Development (TDD)是在要检查的代码之前编写单元测试的时候。TDD就像在写一本书之前为它创建一个大纲。它旨在帮助开发人员编写更简单,更易读,更高效的代码。

显然全球天气文章没有关注TDD无论如何,TDD这不是我们的主题。

.Net Core中的单元测试

创建xUnit测试项目

xUnit.net是一个免费的,开源的,以社区为中心的.NET Framework单元测试工具。xUnit.net是由NUnit v2的原始发明者编写的,是用于测试C#,F#,VB.NET和其他.NET语言的最新技术。xUnit.net适用于ReSharper,CodeRush,TestDriven.NET和Xamarin。

现在我向您展示如何为ASP .Net Core创建xUnit测试项目。在Solution Explorer中添加新项目Weather.Test。

带有.NET Core 2.2的Angular 7的图像2  - 全球天气(第3部分)

选择“xUnit Test Project(.Net Core)”模板并将项目命名为“Weather.Test”。单击“确定”。Weather.Test项目是在GlobalWeather解决方案下创建的。

带有.NET Core 2.2的Angular 7的图像3  - 全球天气(第3部分)

删除UnitTest1.cs。右键单击Weather.Test项目以选择“Manage Nuget Packages”。

添加Micorsoft.AspNetCore,Microsoft.AspNetCore.Mvc,Mircosoft.EntityFrameworkCore和Microsoft.Extensions.DependencyInjection。

除了这些常见的包之外,我们还需要添加Microsoft.EntityFrameworkCore.InMemory,NSubstitute,Shouldly和TestStack.BDDfy。

带有.NET Core 2.2的Angular 7的图像4  - 全球天气(第3部分)

然后添加对其他两个项目的引用,GlobalWeather和Weather.Persistence。

带有.NET Core 2.2的Angular 7的图像5  - 全球天气(第3部分)

什么是Bddfy?

BDDfy是.Net最简单的BDD框架。该名称来自于它允许您简单地将测试转换为BDD行为的事实。什么是BDD行为?

简单来说,BDD行为是Given,When和Then。

Given-When-Then 是一种代表测试的风格 - 或者正如其倡导者所说 - 使用SpecificationByExample指定系统的行为。

基本思想是将场景(或测试)分解为三个部分:

given在开始您在此方案中指定的行为之前,部分描述了世界的状态。您可以将其视为测试的前提条件。

when部分是您指定的行为。

最后,该then 部分描述了由于指定的行为而您期望的更改。

单元测试存储库通用类

右键单击Weather.Test项目,添加Persistence文件夹。因为持久性测试需要模拟数据库,所以MockDatabaseHelper使用Microsoft.EntityFrameworkCore.InMemory 创建类。

隐藏   收缩 带有.NET Core 2.2的Angular 7的图像6  - 全球天气(第3部分)   复制代码

public static class MockDatabaseHelper
{    public static DbContextOptions<WeatherDbContext> CreateNewContextOptions(string databaseName)
    {        //Create a fresh service provider, and therefore a fresh
    
        // InMemory database instance
    
        var serviceProvider = new ServiceCollection()
    
            .AddEntityFrameworkInMemoryDatabase()
    
            .BuildServiceProvider();    
    
        // Create a new options instance telling the context to use an
    
        // InMemory database and the new service provider
    
        var builder = new DbContextOptionsBuilder<WeatherDbContext>();
    
        builder.UseInMemoryDatabase(databaseName)
    
            .UseInternalServiceProvider(serviceProvider);    
        return builder.Options;
    }

}

我们首先为通用存储库类创建单元测试。创建一个名为的新C#文件RepositoryTest.cs添加以下代码:

隐藏   收缩 带有.NET Core 2.2的Angular 7的图像7  - 全球天气(第3部分)   复制代码

using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using Weather.Persistence.Config;using Weather.Persistence.Models;using Weather.Persistence.Repositories;using Microsoft.EntityFrameworkCore;using Microsoft.Extensions.Options;using NSubstitute;using Serilog;using Shouldly;using TestStack.BDDfy;using Xunit;namespace Weather.Test.Persistence
{    public class RepositoryTest
    {        private DbContextOptions<WeatherDbContext> _contextOptions;        private City _testData;        private WeatherDbContext _appContext;        private IOptions<DbContextSettings> _settings;        private IDbContextFactory _dbContextFactory;        private Repository<City> _subject;        private City _result;        public RepositoryTest()
        {
            _testData = new City { Id = "26216", Name = "Melbourne", CountryId = "AU", AccessedDate = new DateTime(2018, 12, 29, 10, 1, 2) };

        }
}

然后添加测试用例。[Fact]属性表示由测试运行器运行的测试方法。

第一个测试是测试是否正确地在数据库中创建新城市。

隐藏   收缩 带有.NET Core 2.2的Angular 7的图像8  - 全球天气(第3部分)   复制代码

#region Facts[Fact]public void CreateCityShouldSucceed()
{    this.Given(x => GivenADatabase("TestDb"))
        .And(x => GivenTheDatabaseHasCities(1))
        .When(x => WhenCreateIsCalledWithTheCityAsync(_testData))
        .Then(x => ThenItShouldReturnTheCity(_testData))
        .BDDfy();
}#endregion#region Givensprivate void GivenADatabase(string context)
{
    _contextOptions = MockDatabaseHelper.CreateNewContextOptions(context);
    _appContext = new WeatherDbContext(_contextOptions);
    _settings = Substitute.For<IOptions<DbContextSettings>>();

    _settings.Value.Returns(new DbContextSettings { DbConnectionString = "test" });
    _dbContextFactory = Substitute.For<IDbContextFactory>();
    _dbContextFactory.DbContext.Returns(_appContext);
    _subject = new Repository<City>(_dbContextFactory, Substitute.For<ILogger>());
}private void GivenTheDatabaseHasCities(int numberOfCities)
{    var cities = new List<City>();    for (var item = 0; item < numberOfCities; item++)
    {
        cities.Add(            new City()
            {
                Id = (item + 1).ToString(),
                Name = $"City{item}",
                CountryId = "AU",
                AccessedDate = DateTimeOffset.UtcNow,
            }
        );
    }

    _appContext.Cities.AddRange(cities);
    _appContext.SaveChanges();
}#endregion#region Whensprivate async Task<bool> WhenCreateIsCalledWithTheCityAsync(City city)
{
    _result = await _subject.AddEntity(city);    return true;
}#endregion#region Thensprivate void ThenItShouldReturnTheCity(City city)
{
    _result.Id.ShouldBe(city.Id);
}#endregion

GivenADatabase method是一个在内存中创建数据库上下文的设置步骤。

GivenTheDatabaseHasCities method是一个设置步骤,用于在Cities表中添加城市条目。

WhenCreateIsCalledWithTheCityAsync 方法是一个被考虑的状态转换步骤,它调用AddEntity方法。

ThenItShouldReturnTheCity 方法是断言步骤。

在这个测试中,我们正在使用NSubstitue和Shouldly。

NSubstitue和应该

NSubstitue是.NET模拟框架的友好替代品。

编写单元测试时,有时需要模拟被测对象(SUT)的依赖关系。到目前为止,最简单的方法是使用模拟库,它具有额外的好处,它允许您通过检查它与模拟的交互来验证SUT的行为。

NSubstitue和Moq是两个最受欢迎的.Net模拟框架。但是,NSubstitute具有比Moq更清晰的语法,它支持开箱即用的上下文/规范样式。

应该是另一个测试框架,它提高了测试代码的可读性并具有更好的测试失败消息。Shouldly的一个好处是它可以帮助提高测试代码的可读性。它以两种方式实现这一点:消除预期和实际值的歧义,并生成流畅可读的代码。

运行和调试单元测试

带有.NET Core 2.2的Angular 7的图像9  - 全球天气(第3部分)

运行后,您可以在Test Explorer中看到结果。

带有.NET Core 2.2的Angular 7的图像10  - 全球天气(第3部分)

现在我们添加其他测试:CreateCityShouldThrowException()GetCityShouldSucceed()UpdateCityShouldSucceed()DeleteCityShouldSucceed()

CreateCityShouldThrowException

隐藏   复制代码

[Fact]public void CreateCityShouldThrowException()
{    this.Given(x => GivenADatabase("TestDb"))
        .Given(x => GivenTheDatabaseHasACity(_testData))
        .When(x => WhenCreateSameIdIsCalledWithTheCityAsync(_testData))
        .Then(x => ThenItShouldBeSuccessful())
        .BDDfy();
}private void GivenTheDatabaseHasACity(City city)
{
    _appContext.Cities.Add(city);
    _appContext.SaveChanges();
}private async Task WhenCreateSameIdIsCalledWithTheCityAsync(City city)
{    await Assert.ThrowsAsync<ArgumentException>(async () => await _subject.AddEntity(city));
}private void ThenItShouldBeSuccessful()
{ }

GetCityShouldSucceed

隐藏   复制代码

[Fact]public void GetCityShouldSucceed()
{    this.Given(x => GivenADatabase("TestDb"))
        .Given(x => GivenTheDatabaseHasACity(_testData))
        .When(x => WhenGetCalledWithTheCityIdAsync(_testData.Id))
        .Then(x => ThenItShouldReturnTheCity(_testData))
        .BDDfy();

}private async Task<bool> WhenGetCalledWithTheCityIdAsync(string id)
{
    _result = await _subject.GetEntity(id);    return true;
}

UpdateCityShouldSucceed

隐藏   复制代码

[Fact]public void UpdateCityShouldSucceed()
{    var city = new City
    {
        Id = _testData.Id,
        Name = "Melbourne",
        CountryId = "AU",
        AccessedDate = new DateTime(2018, 12, 30, 10, 1, 2)
    };    this.Given(x => GivenADatabase("TestDb"))
        .Given(x => GivenTheDatabaseHasACity(_testData))
        .When(x => WhenUpdateCalledWithTheCityAsync(city))
        .Then(x => ThenItShouldReturnTheCity(city))
        .BDDfy();
}private async Task<bool> WhenUpdateCalledWithTheCityAsync(City city)
{    var entity = await _subject.GetEntity(city.Id);
    entity.Name = city.Name;
    entity.CountryId = city.CountryId;
    entity.AccessedDate = city.AccessedDate;
    _result = await _subject.UpdateEntity(entity);    return true;
}

DeleteCityShouldSucceed

隐藏   复制代码

[Fact]public void DeleteCityShouldSucceed()
{    this.Given(x => GivenADatabase("TestDb"))
        .Given(x => GivenTheDatabaseHasACity(_testData))
        .When(x => WhenDeleteCalledWithTheCityIdAsync(_testData.Id))
        .Then(x => ThenItShouldBeNoExistCity())
        .BDDfy();
}private async Task<bool> WhenDeleteCalledWithTheCityIdAsync(string id)
{    await _subject.DeleteEntity(id);    return true;
}private void ThenItShouldBeNoExistCity()
{
    _appContext.Cities.Count().ShouldBe(0);
}

API控制器的单元测试

设置控制器操作的单元测试以关注控制器的行为。控制器单元测试可避免过滤器,路由和模型绑定等情况。覆盖集体响应请求的组件之间的交互的测试集成测试处理  

在Weather.Test项目中创建“Controllers”文件夹。添加一个名为的类CitiesController.cs,并使用以下代码替换代码。

隐藏   收缩 带有.NET Core 2.2的Angular 7的图像11  - 全球天气(第3部分)   复制代码

using System;using System.Threading.Tasks;using GlobalWeather.Controllers;using GlobalWeather.Services;using NSubstitute;using Serilog;using TestStack.BDDfy;using Xunit;using Microsoft.AspNetCore.Mvc;using Weather.Persistence.Models;namespace Weather.Test.Controllers
{    public class CitiesControllerTest
    {        private ICityService _service;        private CitiesController _controller;        private City _testData;        private ActionResult<City> _result;        #region Facts        [Fact]        public void GetReturnsExpectedResult()
        {            this.Given(x => GivenCitiesControllerSetup())
                .And(x => GivenGeLastAccessedCityReturnsExpected())
                .When(x => WhenGetCalledAsync())
                .Then(x => ThenResultShouldBeOk())
                .BDDfy();
        }

        [Fact]        public void PostCallService()
        {            this.Given(x => GivenCitiesControllerSetup())
                .When(x => WhenPostCalledAsync())
                .Then(x => ThenItShouldCallUpdateAccessedCityInService())
                .BDDfy();
        }        #endregion
        #region Gievns
        private void GivenCitiesControllerSetup()
        {
            _testData = new City
            { Id = "26216", Name = "Melbourne", CountryId = "AU", AccessedDate = DateTimeOffset.UtcNow };
            _service = Substitute.For<ICityService>();
            _controller = new CitiesController(_service, Substitute.For<ILogger>());
        }        private void GivenGeLastAccessedCityReturnsExpected()
        {
            _service.GetLastAccessedCityAsync().Returns(new City());
        }        #endregion
        #region Whens        private async Task WhenGetCalledAsync()
        {
            _result = await _controller.Get();
        }        private async Task WhenPostCalledAsync()
        {            await _controller.Post(_testData);
        }        #endregion
        #region Thens        private void ThenResultShouldBeOk()
        {
            Assert.NotNull(_result);
            Assert.IsType<City>(_result.Value);
        }        private void ThenItShouldCallUpdateAccessedCityInService()
        {
            _service.Received().UpdateLastAccessedCityAsync(_testData);
        }        #endregion
    }
}

如前所述,在控制器单元测试中,我们用替代模拟服务。然后为http get和http post编写测试。

在上面的代码中,我们使用_service.Received().UpdateLastAccessedCityAsync(_testData)在某些情况下(特别是对于void方法),检查替换者是否已收到特定呼叫很有用。可以使用Received()扩展方法检查,然后检查调用。

在Visual Studio 2017中运行测试

您现在可以运行测试了。将测试使用[Fact]属性标记的所有方法。从“测试”菜单项中,运行测试。

带有.NET Core 2.2的Angular 7的图像12  - 全球天气(第3部分)

打开“测试资源管理器”窗口,注意测试结果。

带有.NET Core 2.2的Angular 7的图13  - 全球天气(第3部分)

Angular 7中的单元测试

在这里,我们将使用Jasmine和Karma来测试我们的Angular 7应用程序。

茉莉花

Jasmine是一个JavaScript的开源测试框架。

在开始之前,您需要了解Jasmine的基础知识。

describe- 是具有单个测试规范集合的函数。

test spec - 它只有一个或多个测试期望。

在执行或执行我们的测试用例之前,我们需要插入一些模拟数据或者我们需要做一些清洁活动,出于这些目的,我们有

beforeAll- 在运行测试套件中的所有规范之前调用此函数一次。

afterAll- 完成测试套件中的所有规范后,将调用此函数一次。

beforeEach- 在每个测试规范之前调用此函数。

afterEach- 在每个测试规范之后调用此函数。

因果报应

它只是一个测试运行者。它是一个工具,它允许我们从命令行生成浏览器并在其中运行茉莉花测试。测试结果也显示在命令行中。

在Angular 7中编写单元测试规范

Angular CLI下载并安装使用Jasmine测试框架测试Angular应用程序所需的一切。

当我们使用Angular CLI命令创建组件和服务时,已经创建了默认测试规范。例如,app.component.spec.ts

import { TestBed, async } from '@angular/core/testing';import { RouterTestingModule } from '@angular/router/testing';import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));

  it('should create the app', () => {    const fixture = TestBed.createComponent(AppComponent);    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  });

  it(`should have as title 'WeatherClient'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app.title).toEqual('WeatherClient');
  });

  it('should render title in a h1 tag', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('Welcome to WeatherClient!');
  });
});

如果您运行ng test,karma将打开您的浏览器,您可以在其中查看测试结果。启动PowerShell,转到GlobalWeather \ GlobalWeather \ WeatherClient文件夹。运行以下命令;

ng test

 Karma打开您的浏览器,我假设您将Chrome设置为默认浏览器。

带有.NET Core 2.2的Angular 7的图像15  - 全球天气(第3部分)

您可以看到所有单元测试都失败了。但不要惊慌。大多数错误是由未正确导入的模块引起的。让我们让测试规格有效。首先从app.component.spec.ts开始。

单元测试App组件

我们改变app.component.spec.ts如下:

隐藏   收缩 带有.NET Core 2.2的Angular 7的图像16  - 全球天气(第3部分)   复制代码

import { TestBed, async } from '@angular/core/testing';import { RouterTestingModule } from '@angular/router/testing';import { ReactiveFormsModule } from '@angular/forms';import { NgbModule } from '@ng-bootstrap/ng-bootstrap';import { AppComponent } from './app.component';import { WeatherComponent } from './weather/weather.component';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule,
        ReactiveFormsModule,
        NgbModule
      ],
      declarations: [
        AppComponent,
        WeatherComponent
      ],
    }).compileComponents();
  }));

  it('should create the app', () => {    const fixture = TestBed.createComponent(AppComponent);    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  });

  it(`should have as title 'WeatherClient'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app.title).toEqual('WeatherClient');
  });

});

如果与之前的代码进行比较,您可以看到主要的变化是修复导入,例如导入WeatherComponent,导入ReactiveFormsModule和导入NgbMoudle。除了默认的测试用例,“应该创建应用程序”,添加一个新的,“应该有标题'WeatherClient'”。

让我们通过“ng test”再次运行测试。

带有.NET Core 2.2的Angular 7的图像17  - 全球天气(第3部分)

看,所有错误app.component.spec.ts都消失了,这意味着app.component.ts通过测试。

单元测试城市服务

接下来我们修复cityservice.spec.ts,用下面的代码替换默认代码:

隐藏   收缩 带有.NET Core 2.2的Angular 7的图像18  - 全球天气(第3部分)   复制代码

import { async, TestBed } from '@angular/core/testing';import { HttpClientTestingModule, HttpTestingController, TestRequest } from '@angular/common/http/testing';import { Constants } from '../../../app/app.constants';import { CityService } from './city.service';import { ErrorHandleService } from './error-handle.service';import { CityMetaData } from '../models/city-meta-data';import { City } from '../models/city';

describe('CityService', () => {  let service: CityService;  let httpTestingController: HttpTestingController;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [CityService, ErrorHandleService]
    });
    service = TestBed.get(CityService);
    httpTestingController = TestBed.get(HttpTestingController);
  }));

  afterEach(() => {
    httpTestingController.verify();
  });

  it('should create', () => {
    expect(service).toBeTruthy();
  });

  it('should get last accessed city', () => {    const result = { id: '26216', name: 'Melbourne', countryId: 'AU' } as CityMetaData;

    service.getLastAccessedCity()
      .subscribe(
        (data: City) => expect(data.Key).toEqual('26216'),
        (err) => expect(err).toBeNull()
      );    const uri = decodeURIComponent(`${Constants.cityAPIUrl}`);
    const req: TestRequest = httpTestingController.expectOne(req => req.url.includes(uri));

    expect(req.request.method).toEqual('GET');

    req.flush(result);
  });
});

这里需要提一下的是如何测试http get服务。

建立

我们设置了  TestBed 导入  HttpClientTestingModule 和提供HttpTestingController当然,我们也提供我们正在测试的服务CityService

我们还运行  HttpTestingController#verify 以确保没有未完成的请求:

隐藏   复制代码

afterEach(() => { httpTestingController.verify(); });

惩戒

您可以使用  HttpTestingController 模拟请求和  flush方法来提供虚拟值作为响应。当HTTP请求方法返回一个Observable时,我们订阅它并在回调方法中创建我们的期望:

隐藏   复制代码

it('should get last accessed city', () => {  const result = { id: '26216', name: 'Melbourne', countryId: 'AU' } as CityMetaData;

  service.getLastAccessedCity()
    .subscribe(
      (data: City) => expect(data.Key).toEqual('26216'),
      (err) => expect(err).toBeNull()
    );  const uri = decodeURIComponent(`${Constants.cityAPIUrl}`);
  const req: TestRequest = httpTestingController.expectOne(req => req.url.includes(uri));

  expect(req.request.method).toEqual('GET');

  req.flush(result);
});

使用模拟的请求  expectOne,  expectNone 或 match.

我们准备模拟数据,

隐藏   复制代码

const result = {id: '26216', name: 'Melbourne', countryId: 'AU'} as CityMetaData;

将此模拟数据刷新为http请求。

隐藏   复制代码

req.flush(result);

单元测试当前条件服务

修复current-conditions.service.spec.ts。用以下内容替换默认代码:

隐藏   收缩 带有.NET Core 2.2的Angular 7的图像19  - 全球天气(第3部分)   复制代码

import { async, TestBed } from '@angular/core/testing';import { HttpClientTestingModule, HttpTestingController, TestRequest } from '@angular/common/http/testing';import { Constants } from '../../../app/app.constants';import { CurrentConditionsService } from './current-conditions.service';import { ErrorHandleService } from './error-handle.service';import { CurrentConditions } from '../models/current-conditions';

describe(' CurrentConditionsService', () => {  let service: CurrentConditionsService;  let httpTestingController: HttpTestingController;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [CurrentConditionsService, ErrorHandleService]
    });
    service = TestBed.get(CurrentConditionsService);
    httpTestingController = TestBed.get(HttpTestingController);
  }));

  afterEach(() => {
    httpTestingController.verify();
  });

  it('should create',
    () => {
      expect(service).toBeTruthy();
    });

  it('should get current conditions',
    () => {      const result = [
        {
          LocalObservationDateTime: '',
          WeatherText: 'Sunny',
          WeatherIcon: 1,
          IsDayTime: true,
          Temperature: {
            Imperial: null,
            Metric: {
              Unit: 'C',
              UnitType: 1,
              Value: 36
            }
          }

        }
      ] as CurrentConditions[];

      service.getCurrentConditions('26216')
        .subscribe(
          (data: CurrentConditions[]) => expect(data.length === 1 && data[0].WeatherText === 'Sunny').toBeTruthy(),
          (err: CurrentConditions[]) => expect(err.length).toEqual(0)
        );      const uri = decodeURIComponent(`${Constants.currentConditionsAPIUrl}/26216?apikey=${Constants.apiKey}`);
      const req: TestRequest = httpTestingController.expectOne(req => req.url.includes(uri));

      expect(req.request.method).toEqual('GET');

      req.flush(result);
    });
});

单元测试位置服务

修复location.service.spec.ts用以下内容替换默认代码:

隐藏   收缩 带有.NET Core 2.2的Angular 7的图像20  - 全球天气(第3部分)   复制代码

import { async, TestBed } from '@angular/core/testing';import { HttpClientTestingModule, HttpTestingController, TestRequest } from '@angular/common/http/testing';import { Constants } from '../../../app/app.constants';import { LocationService } from './location.service';import { ErrorHandleService } from './error-handle.service';import { Country } from '../../shared/models/country';import { City } from '../../shared/models/city';

describe('LocationService', () => {  let service: LocationService;  let httpTestingController: HttpTestingController;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [LocationService, ErrorHandleService]
    });
    service = TestBed.get(LocationService);
    httpTestingController = TestBed.get(HttpTestingController);
  }));

  afterEach(() => {
    httpTestingController.verify();
  });

  it('should create', () => {
    expect(service).toBeTruthy();
  });

  it('should get location', () => {    const result = [{
      Key: '26216', EnglishName: 'Melbourne', Type: 'City', Country: {
        ID: 'AU',
        EnglishName: 'Australia'
      }
    }] as City[];

    service.getCities('melbourne', 'AU')
      .subscribe(
        (data: City[]) => expect(data.length === 1 && data[0].Key === '26216').toBeTruthy(),
        (err: City[]) => expect(err.length).toEqual(0)
      );    const uri = decodeURIComponent(      `${Constants.locationAPIUrl}/cities/AU/search?apikey=${Constants.apiKey}&q=melbourne`);
    const req: TestRequest = httpTestingController.expectOne(req => req.url.includes(uri));

    expect(req.request.method).toEqual('GET');

    req.flush(result);
  });

  it('should get countries', () => {
    const result = [{
      ID: 'AU', EnglishName: 'Australia'
    }] as Country[];

    service.getCountries()
      .subscribe(
        (data: Country[]) => expect(data.length === 1 && data[0].ID === 'AU').toBeTruthy(),
        (err: Country[]) => expect(err.length).toEqual(0)
      );
    const uri = decodeURIComponent(`${Constants.locationAPIUrl}/countries?apikey=${Constants.apiKey}`);
    const req: TestRequest = httpTestingController.expectOne(req => req.url.includes(uri));

    expect(req.request.method).toEqual('GET');

    req.flush(result);
  });
});

单元测试天气组件

修复weather.component.spec.ts用以下内容替换默认代码:

隐藏   收缩 带有.NET Core 2.2的Angular 7的图像21  - 全球天气(第3部分)   复制代码

import { async, ComponentFixture, TestBed } from '@angular/core/testing';import { RouterTestingModule } from '@angular/router/testing';import { HttpClientTestingModule } from '@angular/common/http/testing';import { ReactiveFormsModule, FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms';import { NgbModule } from '@ng-bootstrap/ng-bootstrap';import { WeatherComponent } from './weather.component';import { LocationService } from '../shared/services/location.service';import { CurrentConditionsService } from '../shared/services/current-conditions.service';import { CityService } from '../shared/services/city.service';import { ErrorHandleService } from '../shared/services/error-handle.service';

describe('WeatherComponent', () => {  let component: WeatherComponent;  let fixture: ComponentFixture<WeatherComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [WeatherComponent],
      imports: [ReactiveFormsModule, NgbModule, RouterTestingModule, HttpClientTestingModule],
      providers: [LocationService, CurrentConditionsService, CityService, ErrorHandleService]
    })
      .compileComponents();
    fixture = TestBed.createComponent(WeatherComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  }));

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should get invalid form when location field is empty ',
    () => {
      component.ngOnInit();
      expect(component.weatherForm.valid).toEqual(false);
    });

  it('should get valid form when location field has value ',
    () => {
      component.ngOnInit();
      component.cityControl.patchValue("something");
      expect(component.weatherForm.valid).toEqual(true);
    });
});

上面的代码导致编译问题,beause它试图访问weatherFormweather component,但weatherFormprivate所以,只是删除privateweatherFormweather.component.ts

更换 

隐藏   复制代码

private weatherForm: FormGroup;

隐藏   复制代码

weatherForm: FormGroup;

在这里,我们有两个测试用例来验证反应形式。返回weather.component.ts,需要城市字段。

隐藏   复制代码

buildForm(): FormGroup {  return this.fb.group({
    searchGroup: this.fb.group({
      country: [        null
      ],
      city: [        null,
        [Validators.required]
      ],
    })
  });
}

这意味着如果City 输入字段没有值,则表单无效。因为只有一个require字段,所以当您在此输入中输入内容时,表单将变为有效。

以下两个测试用例涵盖了这种行为。

隐藏   复制代码

it('should get invalid form when location field is empty ',
  () => {
    component.ngOnInit();
    expect(component.weatherForm.valid).toEqual(false);
  });

it('should get valid form when location field has value ',
  () => {
    component.ngOnInit();
    component.cityControl.patchValue("something");
    expect(component.weatherForm.valid).toEqual(true);
  });
});

包起来

现在我们再次运行测试,所有测试用例都通过了。

带有.NET Core 2.2的Angular 7的图像22  - 全球天气(第3部分)

结论

UNIT TESTING是软件测试的一个级别,其中测试软件的各个单元/组件。目的是验证软件的每个单元是否按设计执行。在本文中,我讨论了如何在Asp .Net Core和Angular中编写单元测试。

全球天气系列文章涵盖了Angular 7和.Net Core的整个开发周期,从前端到后端以及单元测试。角度和.net核心的学习材料并不多。所以我尝试写一本关于Angular和.Net Core的烹饪书。但是在有限的时间内,写一系列文章是一种可能的解决方案。 

合作伙伴

网站备案:豫ICP备15023476号-1 唯特科技