In my Asp.net Core 2.0 application, I am trying to unit test my data service layer (.Net Standard Class Library) that uses the Microsoft.Extensions.Configuration.IConfigurat
Unit tests have a very useful habit of exposing design issues. In this case you have made some design choices that prove difficult to test because of tight coupling to framework concerns as well as static concerns.
First, it looks like SqlRestaurantDataCL
actually depends on a connection factory
public interface IDbConnectionFactory {
IDbConnection GetConnection();
}
Which would refactor the data implementation as advised to depend on that abstraction.
public class SqlRestaurantDataCL : IRestaurantDataCL {
private readonly IDbConnectionFactory factory;
public SqlRestaurantDataCL(IDbConnectionFactory factory) {
this.factory = factory;
}
public Restaurant Add(Restaurant restaurant) {
using (var connection = factory.GetConnection()) {
string insertSql = @"INSERT INTO [dbo].[RESTAURANTS]([Cuisine], [Name])
OUTPUT INSERTED.*
VALUES (@Cuisine, @Name)";
restaurant = connection.QuerySingle<Restaurant>(insertSql, new {
Cuisine = restaurant.Cuisine,
Name = restaurant.Name
});
return restaurant;
}
}
//...
}
The assumption is that Dapper is being used to make the query above.
With the introduction of the abstracted dependencies, they can be mocked as needed when testing in isolation.
public class SqlRestaurantDataCLUnitTest {
[Fact]
public void AddTest() {
//Arrange
var connection = new Mock<IDbConnection>();
var factory = new Mock<IDbConnectionFactory>();
factory.Setup(_ => _.GetConnection()).Returns(connection.Object);
//...setup the connection to behave as expected
var restaurantDataCL = new SqlRestaurantDataCL(factory.Object);
var restaurant = new Restaurant {
Name = "TestName",
Cuisine = CuisineType.None
};
//Act
var result = restaurantDataCL.Add(restaurant);
//Assert
Assert.IsNotType(null, result.Id);
}
}
Now if you meant to actually touch the real database then this is not an isolation unit test but instead an integration test, that will have a different approach.
In production code, the factory can be implemented
public class SqlDbConnectionFactory : IDbConnectionFactory {
private readonly ConnectionSetings connectionSettings;
SqlDbConnectionFactory(ConnectionSetings connectionSettings) {
this.connectionSettings = connectionSettings;
}
public IDbConnection GetConnection() {
return new SqlConnection(connectionSettings.Name));
}
}
Where ConnectionSetings
is defined as a simple POCO to store the connection string
public class ConnectionSetings {
public string Name { get; set; }
}
In the composition root the settings can be extracted from configurations
IConfiguration Configuration; //this would have been set previously
public void ConfigureServices(IServiceCollection services) {
//...
var settings = Configuration
.GetSection("ConnectionStrings:OdeToFood")
.Get<ConnectionSetings>();
//...verify settings (if needed)
services.AddSingleton(settings);
services.AddSingleton<IDbConnectionFactory,SqlDbConnectionFactory>();
services.AddSingleton<IRestaurantDataCL,SqlRestaurantDataCL>();
//Note: while singleton was used above, You can decide to use another scope
// if so desired.
}
There was really no need to be passing IConfiguration
around as it is more of a framework concern that is really only relevant at start up.