How to use Microsoft.Extensions.Configuration.IConiguration in my XUnit unit testing

后端 未结 1 761
面向向阳花
面向向阳花 2021-01-22 11:08

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

相关标签:
1条回答
  • 2021-01-22 11:58

    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.

    0 讨论(0)
提交回复
热议问题