如何使用 XUnit 对 Web API 控制器进行单元测试

Posted

技术标签:

【中文标题】如何使用 XUnit 对 Web API 控制器进行单元测试【英文标题】:How to unit test a Web API controller using XUnit 【发布时间】:2020-05-25 09:23:54 【问题描述】:

我正在尝试使用 XUnit 在我的 Web API 中对我的控制器内的方法进行单元测试。该方法的作用是从数据库中获取一个按 ISBN 的书名。我在单元测试期间遇到的问题是我不确定如何插入我必须执行测试的虚拟数据,以及 Assert 函数的工作原理。

TitleController.cs

[ApiController]
[Route("titlecontroller")]
public class TitleController : Controller

    private IGtlTitleRepository _gtlTitleRepository;

    public TitleController(IGtlTitleRepository gtlTitleRepository)
    
        _gtlTitleRepository = gtlTitleRepository;
    

    [Route("getTitle/ISBN")]
    [HttpGet()]
    public GtlTitle GetTitle(string ISBN)    
    
        return _gtlTitleRepository.GetTitle(ISBN);
    

IGtlTitleRepository.cs

    public interface IGtlTitleRepository

    GtlTitle GetTitle(string ISBN);

MockGtlTitleRepository.cs

    public class MockGtlTitleRepository : IGtlTitleRepository

    private readonly string _connection;
    public MockGtlTitleRepository(IOptions<ConnectionStringList> connectionStrings)
    
        _connection = connectionStrings.Value.GTLDatabase;
    


    private List<GtlTitle> _titleList;

    public GtlTitle GetTitle(string ISBN)
    
        using (var connection = new SqlConnection(_connection))
        
            connection.Open();
            return connection.QuerySingle<GtlTitle>("GetTitleByISBN", new  ISBN , commandType: CommandType.StoredProcedure);
        
    


对,至于我的测试代码,我可以写下面的代码,但是正如我上面所说的,我想不出一个合适的方法来测试这个方法。

 public class UnitTest1

    [Fact]
    public void Test1()
    

        var repositoryMock = new Mock<IGtlTitleRepository>();
        var title = new GtlTitle();
        repositoryMock.Setup(r => r.GetTitle("978-0-10074-5")).Returns(title);
        var controller = new TitleController(repositoryMock.Object);

        var result = controller.GetTitle("978-0-10074-5");
       // assert??
        repositoryMock.VerifyAll();
    

在这个单元测试中应该做什么才能正确地测试方法?

编辑:

GtlTitle.cs

public class GtlTitle

    public string ISBN  get; set; 
    public string VolumeName  get; set; 
    public string TitleDescription  get; set; 
    public string FirstName  get; set; 
    public string LastName  get; set; 
    public string PublisherName  get; set; 


【问题讨论】:

你能告诉我们GtlTitle类吗? 我用它编辑了原帖! 只需检查控制器的结果是否等于标题Assert.Equal(title, result);? 看看这个帖子:exceptionnotfound.net/… ...应该对你有用 @PavelAnikhouski 谢谢!那行代码有效,但是……是这样吗?我假设repositoryMock.Setup(r =&gt; r.GetTitle("978-0-10074-5")).Returns(title); 这行基本上模拟了一个数据库对象? 【参考方案1】:

在进行测试之前,我建议在您的代码中更新一些内容:

使您的存储库方法和控制器操作异步(因此 Web 服务器可以在等待数据库往返之前的调用时处理请求) 使用ActionResult 作为操作返回类型。这样您就可以向客户端发送不同的 http 状态码。 在找不到标题时返回 404 NotFound 状态代码,而不是返回成功结果并以 null 作为有效负载。 考虑对 API 端点使用 RESTful 方法。例如。标题资源的基本 uri 应该类似于 api/titles 不要指定 getTitle 来获取标题端点,因为您知道 HTTP 动词映射到哪个端点 (GET) 和基本资源 url (api/titles)。

应用这些注释:

[ApiController]
[Route("api/titles")]
public class TitleController : Controller

    private IGtlTitleRepository _gtlTitleRepository;

    public TitleController(IGtlTitleRepository gtlTitleRepository)
    
        _gtlTitleRepository = gtlTitleRepository;
    

    [HttpGet("ISBN")] // GET api/titles/ISBN
    public async Task<ActionResult<GtlTitle>> GetTitle(string ISBN)    
    
        var title = await _gtlTitleRepository.GetTitle(ISBN);
        if (title == null)
            return NotFound();

        return title;
    

测试标题检索成功:

[Fact]
public async Task Should_Return_Title_When_Title_Found()

    var repositoryMock = new Mock<IGtlTitleRepository>();
    var title = new GtlTitle();
    repositoryMock.Setup(r => r.Get("978-0-10074-5")).Returns(Task.FromResult(title));

    var controller = new TitleController(repositoryMock.Object);

    var result = await controller.GetTitle("978-0-10074-5");
    Assert.Equal(title, result.Value);

找不到标题时:

[Fact]
public async Task Should_Return_404_When_Title_Not_Found()

    var repositoryMock = new Mock<IGtlTitleRepository>();
    repositoryMock.Setup(r => r.Get("978-0-10074-5")).Returns(Task.FromResult<GtlTitle>(null));

    var controller = new TitleController(repositoryMock.Object);

    var result = await controller.GetTitle("978-0-10074-5");
    Assert.IsType<NotFoundResult>(result.Result);

【讨论】:

非常感谢您的详细回复!它非常有用。 我猜,你应该在控制器中使用 return Ok(title); 而不是 return title; 之类的东西 在线await _gtlTitleRepository.GetTitle(ISBN);,它说`GtlTitle 不包含GetAwaiter`的定义。有什么建议吗?

以上是关于如何使用 XUnit 对 Web API 控制器进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

.Net Core 5 Web Api - Xunit 没有读取我的 appsettings.Development

如何从我的项目访问我的MongoDB实例到我的xunit测试项目类?

使用Rider中搭建specflow+xunit+selenium对web页面进行自动化功能测试环境

xUnit 无法输出到控制台 .NET

如何使用 FluentAssertions 在 XUnit 中测试 MediatR 处理程序

如何使用FluentAssertions在XUnit中测试MediatR处理程序