XUnit 依赖注入

Intro

现在的开发中越来越看重依赖注入的思想,微软的 Asp.Net Core 框架更是天然集成了依赖注入,那么在单元测试中如何使用依赖注入呢?

本文主要介绍如何通过 XUnit 来实现依赖注入, XUnit 主要借助 SharedContext 来共享一部分资源包括这些资源的创建以及释放。

Scoped

针对 Scoped 的对象可以借助 XUnit 中的 IClassFixture 来实现

  1. 定义自己的 Fixture,需要初始化的资源在构造方法里初始化,如果需要在测试结束的时候释放资源需要实现 IDisposable 接口
  2. 需要依赖注入的测试类实现接口 IClassFixture<Fixture>
  3. 在构造方法中注入实现的 Fixture 对象,并在构造方法中使用 Fixture 对象中暴露的公共成员

Singleton

针对 Singleton 的对象可以借助 XUnit 中的 ICollectionFixture 来实现

  1. 定义自己的 Fixture,需要初始化的资源在构造方法里初始化,如果需要在测试结束的时候释放资源需要实现 IDisposable 接口
  2. 创建 CollectionDefinition,实现接口 ICollectionFixture<Fixture>,并添加一个 [CollectionDefinition("CollectionName")] Attribute,CollectionName 需要在整个测试中唯一,不能出现重复的 CollectionName
  3. 在需要注入的测试类中添加 [Collection("CollectionName")] Attribute,然后在构造方法中注入对应的 Fixture

Tips

  • 如果有多个类需要依赖注入,可以通过一个基类来做,这样就只需要一个基类上添加 [Collection("CollectionName")] Attribute,其他类只需要集成这个基类就可以了

Samples

Scoped Sample

这里直接以 XUnit 的示例为例:

public class DatabaseFixture : IDisposable
{
public DatabaseFixture()
{
Db = new SqlConnection("MyConnectionString"); // ... initialize data in the test database ...
} public void Dispose()
{
// ... clean up test data from the database ...
} public SqlConnection Db { get; private set; }
} public class MyDatabaseTests : IClassFixture<DatabaseFixture>
{
DatabaseFixture fixture; public MyDatabaseTests(DatabaseFixture fixture)
{
this.fixture = fixture;
} [Fact]
public async Task GetTest()
{
// ... write tests, using fixture.Db to get access to the SQL Server ...
// ... 在这里使用注入 的 DatabaseFixture
}
}

Singleton Sample

这里以一个对 asp.net core API 的测试为例

  1. 自定义 Fixture
/// <summary>
/// Shared Context https://xunit.github.io/docs/shared-context.html
/// </summary>
public class APITestFixture : IDisposable
{
private readonly IWebHost _server;
public IServiceProvider Services { get; } public HttpClient Client { get; } public APITestFixture()
{
var baseUrl = $"http://localhost:{GetRandomPort()}";
_server = WebHost.CreateDefaultBuilder()
.UseUrls(baseUrl)
.UseStartup<TestStartup>()
.Build();
_server.Start(); Services = _server.Services; Client = new HttpClient(new WeihanLi.Common.Http.NoProxyHttpClientHandler())
{
BaseAddress = new Uri($"{baseUrl}")
};
// Add Api-Version Header
// Client.DefaultRequestHeaders.TryAddWithoutValidation("Api-Version", "1.2"); Initialize(); Console.WriteLine("test begin");
} /// <summary>
/// TestDataInitialize
/// </summary>
private void Initialize()
{
} public void Dispose()
{
using (var dbContext = Services.GetRequiredService<ReservationDbContext>())
{
if (dbContext.Database.IsInMemory())
{
dbContext.Database.EnsureDeleted();
}
} Client.Dispose();
_server.Dispose(); Console.WriteLine("test end");
} private static int GetRandomPort()
{
var random = new Random();
var randomPort = random.Next(10000, 65535); while (IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners().Any(p => p.Port == randomPort))
{
randomPort = random.Next(10000, 65535);
} return randomPort;
}
} [CollectionDefinition("APITestCollection")]
public class APITestCollection : ICollectionFixture<APITestFixture>
{
}
  1. 自定义Collection
[CollectionDefinition("TestCollection")]
public class TestCollection : ICollectionFixture<TestStartupFixture>
{
}
  1. 自定义一个 TestBase
[Collection("APITestCollection")]
public class ControllerTestBase
{
protected HttpClient Client { get; } protected IServiceProvider Services { get; } public ControllerTestBase(APITestFixture fixture)
{
Client = fixture.Client;
Services = fixture.Services;
}
}
  1. 需要依赖注入的Test类写法
public class NoticeControllerTest : ControllerTestBase
{
public NoticeControllerTest(APITestFixture fixture) : base(fixture)
{
} [Fact]
public async Task GetNoticeList()
{
using (var response = await Client.GetAsync("/api/notice"))
{
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var responseString = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<PagedListModel<Notice>>(responseString);
Assert.NotNull(result);
}
} [Fact]
public async Task GetNoticeDetails()
{
var path = "test-notice";
using (var response = await Client.GetAsync($"/api/notice/{path}"))
{
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var responseString = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<Notice>(responseString);
Assert.NotNull(result);
Assert.Equal(path, result.NoticeCustomPath);
}
} [Fact]
public async Task GetNoticeDetails_NotFound()
{
using (var response = await Client.GetAsync("/api/notice/test-notice1212"))
{
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
}

运行测试,查看我们的 APITestFixture 是不是只实例化了一次,查看输出日志:

可以看到我们输出的日志只有一次,说明在整个测试过程中确实只实例化了一次,只会启动一个 web server,确实是单例的

Memo

微软推荐的是用 Microsoft.AspNetCore.Mvc.Testing 组件去测试 Controller,但是个人感觉不如自己直接去写web 服务去测试,如果没必要引入自己不熟悉的组件最好还是不要去引入新的东西,否则可能就真的是踩坑不止了。

Reference

XUnit 依赖注入的更多相关文章

  1. 在 xunit 测试项目中使用依赖注入

    在 xunit 测试项目中使用依赖注入 Intro 之前写过几篇 xunit 依赖注入的文章,今天这篇文章将结合我在 .NET Conf 上的分享,更加系统的分享一下在测试中的应用案例. 之所以想分享 ...

  2. 在 XUnit 中使用依赖注入

    在 XUnit 中使用依赖注入 Intro 之前写过一篇 xunit 的依赖注入相关的文章,但是实际使用起来不是那么方便 今天介绍一个基于xunit和微软依赖注入框架的"真正"的依 ...

  3. 更优雅的在 Xunit 中使用依赖注入

    Xunit.DependencyInjection 7.0 发布了 Intro 上次我们已经介绍过一次大师的 Xunit.DependencyInjection https://www.cnblogs ...

  4. C#中的依赖注入和IoC容器

    在本文中,我们将通过用C#重构一个非常简单的代码示例来解释依赖注入和IoC容器. 简介: 依赖注入和IoC乍一看可能相当复杂,但它们非常容易学习和理解. 在本文中,我们将通过在C#中重构一个非常简单的 ...

  5. webapi - 使用依赖注入

    本篇将要和大家分享的是webapi中如何使用依赖注入,依赖注入这个东西在接口中常用,实际工作中也用的比较频繁,因此这里分享两种在api中依赖注入的方式Ninject和Unity:由于快过年这段时间打算 ...

  6. ASP.NET Core 中文文档 第四章 MVC(3.8)视图中的依赖注入

    原文:Dependency injection into views 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:孟帅洋(书缘) ASP.NET Core 支持在视图中使用 依赖 ...

  7. 在WPF中使用依赖注入的方式创建视图

    在WPF中使用依赖注入的方式创建视图 0x00 问题的产生 互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少.我之前主要做W ...

  8. MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息

    MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...

  9. .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整-控制反转和依赖注入的使用

    再次调整项目架构是因为和群友dezhou的一次聊天,我原来的想法是项目尽量做简单点别搞太复杂了,仅使用了DbContext的注入,其他的也没有写接口耦合度很高.和dezhou聊过之后我仔细考虑了一下, ...

随机推荐

  1. [Swift]LeetCode11. 盛最多水的容器 | Container With Most Water

    Given n non-negative integers a1, a2, ..., an , where each represents a point at coordinate (i, ai). ...

  2. [SQL]LeetCode197. 上升的温度 | Rising Temperature

    SQL架构 Create table If Not Exists Weather (Id int, RecordDate date, Temperature int) Truncate table W ...

  3. [Swift]LeetCode739. 每日温度 | Daily Temperatures

    Given a list of daily temperatures T, return a list such that, for each day in the input, tells you ...

  4. [Swift]LeetCode788. 旋转数字 | Rotated Digits

    X is a good number if after rotating each digit individually by 180 degrees, we get a valid number t ...

  5. [Swift]LeetCode866. 回文素数 | Prime Palindrome

    Find the smallest prime palindrome greater than or equal to N. Recall that a number is prime if it's ...

  6. PowerShell 中 RunspacePool 执行异步多线程任务

    在 PowerShell 中要执行任务脚本,现在通常使用 Runspace,效率很高:任务比较多时,用 Runspace pool 来执行异步操作,可以控制资源池数量,就像 C# 中的线程池一样 == ...

  7. 我的.gitignore下配置。存在这里一下。日后有空研究研究!

    .DS_Storenode_modules/dist/yxxt/npm-debug.log*yarn-debug.log*yarn-error.log* # Editor directories an ...

  8. 用abp vNext快速开发Quartz.NET定时任务管理界面

    今天这篇文章我将通过实例代码带着大家一步一步通过abp vNext这个asp.net core的快速开发框架来进行Quartz.net定时任务调度的管理界面的开发.大伙最好跟着一起敲一下代码,当然源码 ...

  9. Nancy in .Net Core学习笔记 - 初识Nancy

    前言 去年11月份参加了青岛MVP线下活动,会上老MVP衣明志介绍了Nancy, 一直没有系统的学习一下,最近正好有空,就结合.NET Core学习总结了一下. 注: 本文中大部分内容都是对官网文档的 ...

  10. eclipse连接Android8.0以上手机无法打印日志的问题

    概述 我的eclipse的环境: Eclipse:Eclipse IDE for Java DevelopersVersion: Luna Service Release 2 (4.4.2) Andr ...