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);
}
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, useAll calls to DynamoDb should be encapsulated on a separated class called, for example, DynamoDbManager
Now your controller will be changed in this way ValuesController
Refactoring is finished, start writing unit tests ValuesControllerTests
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