如何为生产代码实现Factory,为单元测试实现依赖注入

时间:2017-11-06 23:27:15

标签: c# unit-testing dependency-injection encapsulation factory-pattern

我想给我的类调用者按名称选择提供者,而不是像标准DI推荐的那样传递提供者具体类。它将允许隐藏客户端的实际实现细节,仍然可以控制使用哪个提供程序。我们通过实施工厂

来完成它
public ICurrencyProvider GetCurrencyServiceProvider(string providerName)
    {
        switch (providerName)
        {
            case "CurrencyLayerAPI":
                {  currencyService = new CurrencyLayerWrapper(); }
                break;
            case "XE":
                { currencyProvider = new XEWrapper(); }
                break;
            }
        return _currencyProvider;
    }

和constuctor期望providerName作为参数。

然而,对于单元测试,我希望使用替代,而不是具体的提供者类。 我最终得到了2个参数,负责生产代码的相同选择名称和测试调用的接口。

    public CurrencyProcessor(string providerName, ICurrencyProvider substituteCurrencyProvider =null)
   {
          if(!providerName .IsNullOrEmpty()) 
          {         
             _currencyProvider = GetCurrencyServiceProvider(providerName);
            }
          else
          {  _currencyProvider =substituteCurrencyProvider; 
          }
    }

稍微替代的实现是从配置中读取providerName,而不是将其作为参数传递。

public CurrencyProcessor(IConfigurationProvider configurationProvider,  ICurrencyProvider substituteCurrencyProvider =null)
 {
     _providerName = _configurationProvider.GetAppSetting("CurrencyProviderToUse"); 
      if(!providerName .IsNullOrEmpty()) 
      {         
         _currencyProvider = GetCurrencyServiceProvider(providerName);
      }
      else
      {  _currencyProvider =substituteCurrencyProvider;
      }
}

我徘徊,是否有更好的方法可以让单个参数来控制内部对象的创建,但是避免承担向客户端创建对象的责任。

相关讨论 How to use Dependency Injection without breaking encapsulation?
Preferable way of making code testable: Dependency injection vs encapsulation
https://softwareengineering.stackexchange.com/questions/344442/dependency-injection-with-default-construction

3 个答案:

答案 0 :(得分:1)

因为在你的构造函数中你是静态创建你的提供者,只需注入提供者。

按照你的描述创建一个工厂......

 public class CurrencyFactory
    {
        public static ICurrencyProvider  GetCurrencyServiceProvider(string providerName)
        {
            return null;
        }
    }

然后使用标准依赖注入: -

public class CurrencyProcessor
{
    private ICurrencyProvider _currencyProvider;

    public CurrencyProcessor(ICurrencyProvider currencyProvider)
    {
        _currencyProvider = currencyProvider;
    }
}

然后像这样使用

var p = new CurrencyProcessor(CurrencyFactory.GetCurrencyServiceProvider("bitcoin"));

然后在你的测试中嘲笑它

var mock = new Mock<ICurrencyProvider>(). // mock stuff

答案 1 :(得分:0)

不确定我是否理解正确。

对我来说,听起来你想拥有2个不同的工厂。

首先创建一个接口:

public interface ICurrencyProviderFactory
{
    ICurrencyProvider Create()
}

然后创建配置工厂:

public class ConfigurationCurrencyProviderFactory : ICurrencyProviderFactory
{
    public ConfigurationCurrencyProviderFactory(IConfigurationProvider configuration)
    {    
    }

    public ICurrencyProvider Create()
    {
    }    
}

然后是UnitTest工厂:

public class UnitTestCurrencyProviderFactory : ICurrencyProviderFactory
{
    public UnitTestCurrencyProviderFactory()
    {    
    }

    public ICurrencyProvider Create()
    {
    }    
}

您的货币处理器应如下所示:

public CurrencyProcessor(ICurrencyProviderFactory factory)
{
    _currencyProvider = factory.Create();
}

在ServiceCollection中,或者在解决依赖关系时,应该包含正确的工厂。

因此,对于Production,您为UnitTest ConfigurationCurrencyProviderFactory添加UnitTestCurrencyProviderFactory。那么您的实际代码应该取决于ICurrencyProviderFactory

答案 2 :(得分:0)

您实际需要与工厂一起申请的是战略模式

interface ICurrencyProvider {
    //...members
}

public interface ICurrencyProviderStrategy {
    string Name { get; }
    ICurrencyProvider Create();
}

public interface ICurrencyProviderFactory {
    ICurrencyProvider GetCurrencyServiceProvider(string providerName);
}

工厂的实施将取决于要求创建所需类型的策略集合。

public class CurrencyProviderFactory : ICurrencyProviderFactory {
    private readonly IEnumerable<ICurrencyProviderStrategy> strategies;

    public CurrencyProviderFactory(IEnumerable<ICurrencyProviderStrategy> strategies) {
        this.strategies = strategies;
    }

    public ICurrencyProvider GetCurrencyServiceProvider(string providerName) {
        var provider = strategies.FirstOrDefault(p => p.Name == providerName);
        if (provider != null)
            return provider.Create();
        return null;
    }
}

这可以提供更大的灵活性,因为可以注入任何数量的策略。

以下是CurrencyLayerWrapper策略的示例

public class CurrencyLayerWrapperProvider : ICurrencyProviderStrategy {
    public string Name { get { return "CurrencyLayerAPI"; } }
    public ICurrencyProvider Create() {
        return new CurrencyLayerWrapper();
    }
}