如何使用 XUnit 对 Web API 控制器进行单元测试
How to unit test a Web API controller using XUnit
我正在尝试使用 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; }
}
在开始测试之前,我建议在您的代码中更新一些内容:
- 使您的存储库方法和控制器操作异步(因此 Web 服务器可以在等待数据库往返以进行先前调用的同时处理请求)
- 使用
ActionResult
作为动作 return 类型。这样就可以给客户端发送不同的http状态码了。
- Return 找不到标题时的 404 NotFound 状态代码,而不是 return 使用
null
作为有效负载的成功结果。
- 考虑对 API 端点使用 RESTful 方法。例如。标题资源的基本 uri 应该类似于
api/titles
- 不要为获取标题端点指定
getTitle
,因为您知道映射到 (GET) 端点的 HTTP 动词和基础资源 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);
}
我正在尝试使用 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; }
}
在开始测试之前,我建议在您的代码中更新一些内容:
- 使您的存储库方法和控制器操作异步(因此 Web 服务器可以在等待数据库往返以进行先前调用的同时处理请求)
- 使用
ActionResult
作为动作 return 类型。这样就可以给客户端发送不同的http状态码了。 - Return 找不到标题时的 404 NotFound 状态代码,而不是 return 使用
null
作为有效负载的成功结果。 - 考虑对 API 端点使用 RESTful 方法。例如。标题资源的基本 uri 应该类似于
api/titles
- 不要为获取标题端点指定
getTitle
,因为您知道映射到 (GET) 端点的 HTTP 动词和基础资源 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);
}