这是我第一次编写测试用例,但我并没有陷入困境,也不知道如何继续进行下去.
我有以下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核心测试
发布评论