使用Xunit进行.Net核心测试

编程入门 行业动态 更新时间:2024-10-18 14:25:52
本文介绍了使用Xunit进行.Net核心测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

这是我第一次编写测试用例,但我并没有陷入困境,也不知道如何继续进行下去.

我有以下API.在下面的示例中,我有两个要执行测试的端点.

public class ValuesController : Controller { //This interface is used to setup dynamo db and connection to aws private IDynamoDbClientInitialization _clientAccessor; private static string dynamoDbTable = string.Empty; public ValuesController(IOptions<Dictionary<string, string>> appSettings, IDynamoDbClientInitialization clientAccessor) { var vals = appSettings.Value; dynamoDbTable = vals["dynamoDbTable"]; _clientAccessor = clientAccessor; } [HttpGet("api/data")] public async Task<List<MyModel>> GetAllData(string type, string status) { List<ScanCondition> conditions = new List<ScanCondition>(); conditions.Add(new ScanCondition("Type", ScanOperator.Equal, type)); conditions.Add(new ScanCondition("Status", ScanOperator.Equal, status)); var response = await _clientAccessor.GetContext().ScanAsync<MyModel>(conditions, AWSHelperMethods.GetDynamoDbOperationConfig(dynamoDbTable)).GetRemainingAsync(); return results.Select(x => x.UpdatedBy.ToLower()).ToList(); } [HttpPost("api/save")] public async Task<IActionResult> SaveData([FromBody] List<MyModel> listData, string input, string name, string type) { List<MyModel> model = null; foreach (var data in listData) { //populating data here await _clientAccessor.GetContext().SaveAsync(data, AWSHelperMethods.GetDynamoDbOperationConfig(dynamoDbTable)); } return Ok(); } } public class DynamoDbClientInitialization : IDynamoDbClientInitialization { private readonly DynamoDbClientSettings settings; private DynamoDBContext _awsContext; public DynamoDbClientInitialization(IOptions<DynamoDbClientSettings> options) { settings = options?.Value; } public DynamoDBContext GetContext() { //Check is context already exists. If not create a new one. if(_awsContext != null) { return _awsContext; } else { var creds = AWSHelperMethods.SetAwsCredentials(settings.Id, settings.Password); var dynamoClient = AWSHelperMethods.GetDynamoDbClient(creds, settings.Region); _awsContext = AWSHelperMethods.GetDynamoDbContext(dynamoClient); return _awsContext; } } } public static class AWSHelperMethods { public static BasicAWSCredentials SetAwsCredentials(string awsId, string awsPassword) { var creds = new BasicAWSCredentials(awsId, awsPassword); return creds; } public static AmazonDynamoDBClient GetDynamoDbClient(BasicAWSCredentials creds, RegionEndpoint awsDynamoDbRegion) { var client = new AmazonDynamoDBClient(creds, awsDynamoDbRegion); return client; } public static DynamoDBContext GetDynamoDbContext(AmazonDynamoDBClient client) { var context = new DynamoDBContext(client); return context; } public static DynamoDBOperationConfig GetDynamoDbOperationConfig(string dynamoDbTable) { DynamoDBOperationConfig config = new DynamoDBOperationConfig() { OverrideTableName = dynamoDbTable }; return config; } }

下面是我添加的xunit项目.在这里,我使用最小起订量来起订我的aws连接和其他连接.有关代码的注释,请参见下面的问题.

public class DataTest { [Fact] public void PassingTest() { //Arrange var dynamoDbTable = "someValue"; //Trying to moq IOptions var moqOp = new Mock<IOptions<Dictionary<string, string>>>(); //Create an instance to hold desired values var vals = new Dictionary<string, string>(); //Set expected value vals["dynamoDbTable"] = dynamoDbTable; //Setup dependency behavior moqOp.Setup(_ => _.Value).Returns(vals); //Trying to moq my connection var moqDb = new Mock<IDynamoDbClientInitialization>(); //Fake data List<MyModel> data = new List<MyModel>() { //populate as needed }; moqDb .Setup(_ => _.GetContext().ScanAsync<MyModel> (It.IsAny<List<ScanCondition>>(), AWSHelperMethods.GetDynamoDbOperationConfig(dynamoDbTable)).GetRemainingAsync()) .ReturnsAsync(data); ValuesController controller = new ValuesController(moqOp.Object, moqDb.Object); var actual = controller.GetAllData(); } }

上面我得到的错误是: 表达式树可能不包含使用可选参数

的调用或调用

这是在线上

.Setup(_ => _.GetContext().ScanAsync<MyModel> (It.IsAny<List<ScanCondition>>(), AWSHelperMethods.GetDynamoDbOperationConfig(dynamoDbTable)).GetRemainingAsync())

任何人都可以帮助解决吗?

-更新--

public interface IDynamoDbManager { Task<List<T>> GetAsync(IEnumerable<ScanCondition> conditions); Task SaveAsync(T item); }

解决方案

表达式树可能不包含使用以下内容的调用或调用 可选参数

您试图在尚未设置GetContext()的情况下调用"方法ScanAsync.为了解决这个问题,您必须在尝试设置ScanAsync()之前设置GetContext()的返回值.

此代码很难测试,因此让我们对其进行重构.

您必须将直接调用_clientAccessor.GetContext()更改为注入IDynamoDBContext. DynamoDbClientInitialization没有意义,因为可以轻松地将其替换为IAmazonDynamoDb.要摆脱冗长的配置读取代码行,请使用

services.AddAWSService<IAmazonDynamoDB>();

所有对DynamoDb的调用都应封装在一个单独的类中,例如 DynamoDbManager

public class DynamoDbManager<T> : DynamoDBContext, IDynamoDbManager<T> where T : class { private DynamoDBOperationConfig _config; public DynamoDbManager(IAmazonDynamoDB client, string tableName): base(client) { _config = new DynamoDBOperationConfig() { OverrideTableName = tableName }; } public Task<List<T>> GetAsync(IEnumerable<ScanCondition> conditions) { return ScanAsync<T>(conditions, _config).GetRemainingAsync(); } public Task SaveAsync(T item) { return base.SaveAsync(item, _config); } }

现在,您的控制器将以这种方式更改 ValuesController

public class ValuesController : Controller { private readonly IDynamoDbManager<MyModel> _dynamoDbManager; //This interface is used to setup dynamo db and connection to aws private static string dynamoDbTable = string.Empty; public ValuesController(IOptions<Dictionary<string, string>> appSettings, IDynamoDbManager<MyModel> dynamoDbManager) { _dynamoDbManager = dynamoDbManager; var vals = appSettings.Value; dynamoDbTable = vals["dynamoDbTable"]; } [HttpGet("api/data")] public async Task<IActionResult> GetAllData(string type, string status) { var conditions = new List<ScanCondition> { new ScanCondition("Type", ScanOperator.Equal, type), new ScanCondition("Status", ScanOperator.Equal, status) }; var result = await _dynamoDbManager.GetAsync(conditions); var response = result.Select(_ => _.UpdatedBy.ToLower()).ToList(); return Ok(response); } [HttpPost("api/save")] public async Task<IActionResult> SaveData([FromBody] List<MyModel> listData, string input, string name, string type) { foreach (var data in listData) { //populating data here await _dynamoDbManager.SaveAsync(data); } return Ok(); } }

重构完成,开始编写单元测试 ValuesControllerTests

public class ValuesControllerTests { private Mock<IDynamoDbManager<MyModel>> _dbManager; private ValuesController _valuesController; public ValuesControllerTests() { var mockRepository = new MockRepository(MockBehavior.Loose); _dbManager = mockRepository.Create<IDynamoDbManager<MyModel>>(); var options = new OptionsWrapper<Dictionary<string, string>>(new Dictionary<string, string>() { {"dynamoDbTable", nameof(MyModel) } }); _valuesController = new ValuesController(options, _dbManager.Object); } [Fact] public async Task GetAllData_ShouldSelectUpdateByInLowerCase() { // var searchResult = new List<MyModel>() { new MyModel() {UpdatedBy = "UpdatedBy1"} }; _dbManager .Setup(_ => _.GetAsync(It.Is<List<ScanCondition>>(list => list.Count == 2))) .ReturnsAsync(searchResult); // var result = await _valuesController.GetAllData("typeValue", "statusValue"); // var okResult = result as OkObjectResult; Assert.NotNull(okResult); var values = okResult.Value as List<string>; Assert.Contains("updatedby1", values); } [Fact] public async Task SaveData_ShouldCallSaveForAllRequestedData() { // var listData = new List<MyModel>() { new MyModel(), new MyModel(), new MyModel() }; _dbManager .Setup(_ => _.SaveAsync(It.IsAny<MyModel>())) .Returns(Task.CompletedTask); // var result = await _valuesController.SaveData(listData, "","", ""); // _dbManager.Verify(_ => _.SaveAsync(It.IsAny<MyModel>()), Times.Exactly(3)); } }

就这样,我对每个控制器动作都进行了两次测试,因为它更容易理解-如果您真的只需要一个测试,就可以轻松加入它们

This is the first time I am writing test case and I am not sort of stuck and not sure how to proceed further.

I have the following API. In the below sample I have 2 endpoints which I want to perform testing.

public class ValuesController : Controller { //This interface is used to setup dynamo db and connection to aws private IDynamoDbClientInitialization _clientAccessor; private static string dynamoDbTable = string.Empty; public ValuesController(IOptions<Dictionary<string, string>> appSettings, IDynamoDbClientInitialization clientAccessor) { var vals = appSettings.Value; dynamoDbTable = vals["dynamoDbTable"]; _clientAccessor = clientAccessor; } [HttpGet("api/data")] public async Task<List<MyModel>> GetAllData(string type, string status) { List<ScanCondition> conditions = new List<ScanCondition>(); conditions.Add(new ScanCondition("Type", ScanOperator.Equal, type)); conditions.Add(new ScanCondition("Status", ScanOperator.Equal, status)); var response = await _clientAccessor.GetContext().ScanAsync<MyModel>(conditions, AWSHelperMethods.GetDynamoDbOperationConfig(dynamoDbTable)).GetRemainingAsync(); return results.Select(x => x.UpdatedBy.ToLower()).ToList(); } [HttpPost("api/save")] public async Task<IActionResult> SaveData([FromBody] List<MyModel> listData, string input, string name, string type) { List<MyModel> model = null; foreach (var data in listData) { //populating data here await _clientAccessor.GetContext().SaveAsync(data, AWSHelperMethods.GetDynamoDbOperationConfig(dynamoDbTable)); } return Ok(); } } public class DynamoDbClientInitialization : IDynamoDbClientInitialization { private readonly DynamoDbClientSettings settings; private DynamoDBContext _awsContext; public DynamoDbClientInitialization(IOptions<DynamoDbClientSettings> options) { settings = options?.Value; } public DynamoDBContext GetContext() { //Check is context already exists. If not create a new one. if(_awsContext != null) { return _awsContext; } else { var creds = AWSHelperMethods.SetAwsCredentials(settings.Id, settings.Password); var dynamoClient = AWSHelperMethods.GetDynamoDbClient(creds, settings.Region); _awsContext = AWSHelperMethods.GetDynamoDbContext(dynamoClient); return _awsContext; } } } public static class AWSHelperMethods { public static BasicAWSCredentials SetAwsCredentials(string awsId, string awsPassword) { var creds = new BasicAWSCredentials(awsId, awsPassword); return creds; } public static AmazonDynamoDBClient GetDynamoDbClient(BasicAWSCredentials creds, RegionEndpoint awsDynamoDbRegion) { var client = new AmazonDynamoDBClient(creds, awsDynamoDbRegion); return client; } public static DynamoDBContext GetDynamoDbContext(AmazonDynamoDBClient client) { var context = new DynamoDBContext(client); return context; } public static DynamoDBOperationConfig GetDynamoDbOperationConfig(string dynamoDbTable) { DynamoDBOperationConfig config = new DynamoDBOperationConfig() { OverrideTableName = dynamoDbTable }; return config; } }

Below is the xunit project that I added. Here I am using MOQ to moq up my aws connection and others. Questions are below in comments against the code.

public class DataTest { [Fact] public void PassingTest() { //Arrange var dynamoDbTable = "someValue"; //Trying to moq IOptions var moqOp = new Mock<IOptions<Dictionary<string, string>>>(); //Create an instance to hold desired values var vals = new Dictionary<string, string>(); //Set expected value vals["dynamoDbTable"] = dynamoDbTable; //Setup dependency behavior moqOp.Setup(_ => _.Value).Returns(vals); //Trying to moq my connection var moqDb = new Mock<IDynamoDbClientInitialization>(); //Fake data List<MyModel> data = new List<MyModel>() { //populate as needed }; moqDb .Setup(_ => _.GetContext().ScanAsync<MyModel> (It.IsAny<List<ScanCondition>>(), AWSHelperMethods.GetDynamoDbOperationConfig(dynamoDbTable)).GetRemainingAsync()) .ReturnsAsync(data); ValuesController controller = new ValuesController(moqOp.Object, moqDb.Object); var actual = controller.GetAllData(); } }

Above I am getting the error as: An expression tree may not contain a call or invocation that uses optional arguments

This is on line

.Setup(_ => _.GetContext().ScanAsync<MyModel> (It.IsAny<List<ScanCondition>>(), AWSHelperMethods.GetDynamoDbOperationConfig(dynamoDbTable)).GetRemainingAsync())

Can anyone help to resolve?

---updated---

public interface IDynamoDbManager { Task<List<T>> GetAsync(IEnumerable<ScanCondition> conditions); Task SaveAsync(T item); }

解决方案

An expression tree may not contain a call or invocation that uses optional arguments

You are trying to "call" a method ScanAsync on not already set up GetContext(). To solve this you have to Setup return value for GetContext() before you tried to Setup ScanAsync()

This code is very hard to test so let's refactor it.

You have to change direct calls of _clientAccessor.GetContext() to injection of IDynamoDBContext. DynamoDbClientInitialization doesn't make sense as can be easily replaced with IAmazonDynamoDb. To get rid of verbose config reading code lines, use

services.AddAWSService<IAmazonDynamoDB>();

All calls to DynamoDb should be encapsulated on a separated class called, for example, DynamoDbManager

public class DynamoDbManager<T> : DynamoDBContext, IDynamoDbManager<T> where T : class { private DynamoDBOperationConfig _config; public DynamoDbManager(IAmazonDynamoDB client, string tableName): base(client) { _config = new DynamoDBOperationConfig() { OverrideTableName = tableName }; } public Task<List<T>> GetAsync(IEnumerable<ScanCondition> conditions) { return ScanAsync<T>(conditions, _config).GetRemainingAsync(); } public Task SaveAsync(T item) { return base.SaveAsync(item, _config); } }

Now your controller will be changed in this way ValuesController

public class ValuesController : Controller { private readonly IDynamoDbManager<MyModel> _dynamoDbManager; //This interface is used to setup dynamo db and connection to aws private static string dynamoDbTable = string.Empty; public ValuesController(IOptions<Dictionary<string, string>> appSettings, IDynamoDbManager<MyModel> dynamoDbManager) { _dynamoDbManager = dynamoDbManager; var vals = appSettings.Value; dynamoDbTable = vals["dynamoDbTable"]; } [HttpGet("api/data")] public async Task<IActionResult> GetAllData(string type, string status) { var conditions = new List<ScanCondition> { new ScanCondition("Type", ScanOperator.Equal, type), new ScanCondition("Status", ScanOperator.Equal, status) }; var result = await _dynamoDbManager.GetAsync(conditions); var response = result.Select(_ => _.UpdatedBy.ToLower()).ToList(); return Ok(response); } [HttpPost("api/save")] public async Task<IActionResult> SaveData([FromBody] List<MyModel> listData, string input, string name, string type) { foreach (var data in listData) { //populating data here await _dynamoDbManager.SaveAsync(data); } return Ok(); } }

Refactoring is finished, start writing unit tests ValuesControllerTests

public class ValuesControllerTests { private Mock<IDynamoDbManager<MyModel>> _dbManager; private ValuesController _valuesController; public ValuesControllerTests() { var mockRepository = new MockRepository(MockBehavior.Loose); _dbManager = mockRepository.Create<IDynamoDbManager<MyModel>>(); var options = new OptionsWrapper<Dictionary<string, string>>(new Dictionary<string, string>() { {"dynamoDbTable", nameof(MyModel) } }); _valuesController = new ValuesController(options, _dbManager.Object); } [Fact] public async Task GetAllData_ShouldSelectUpdateByInLowerCase() { // var searchResult = new List<MyModel>() { new MyModel() {UpdatedBy = "UpdatedBy1"} }; _dbManager .Setup(_ => _.GetAsync(It.Is<List<ScanCondition>>(list => list.Count == 2))) .ReturnsAsync(searchResult); // var result = await _valuesController.GetAllData("typeValue", "statusValue"); // var okResult = result as OkObjectResult; Assert.NotNull(okResult); var values = okResult.Value as List<string>; Assert.Contains("updatedby1", values); } [Fact] public async Task SaveData_ShouldCallSaveForAllRequestedData() { // var listData = new List<MyModel>() { new MyModel(), new MyModel(), new MyModel() }; _dbManager .Setup(_ => _.SaveAsync(It.IsAny<MyModel>())) .Returns(Task.CompletedTask); // var result = await _valuesController.SaveData(listData, "","", ""); // _dbManager.Verify(_ => _.SaveAsync(It.IsAny<MyModel>()), Times.Exactly(3)); } }

That's all, I did two tests for each controller action because its far more understandable - if you realy need only one test you can easily join them

更多推荐

使用Xunit进行.Net核心测试

本文发布于:2023-11-14 19:49:58,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1588364.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:核心   测试   Xunit   Net

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!