在不创建工厂的情况下注入第三方类

时间:2012-10-21 12:25:41

标签: c# dependency-injection ninject

假设我在C#中有以下代码:

public class AppleTree
{
   public AppleTree()
   {
   }

   public string GetApple
   {
      return new Fruit("Apple").ToString();
   }
}

其中Fruit是没有界面的第三方类。

我想为AppleTree类创建一个单元测试,但我不想运行Fruit类。相反,我想注入Fruit类,以便我可以在测试中模拟它。

我应该怎么做呢?我可以创建一个创建苹果的工厂,然后为这个工厂添加一个接口,如:

public class FruitFactory : IFruitFactory
{
   Fruit CreateApple()
   {
      return new Fruit("Apple");
   }
}

现在我可以将IFruitFactory注入AppleTree并使用CreateApple而不是新的Fruit作为:

public class AppleTree
{
   private readonly IFruitFactory _fruitFactory;

   public AppleTree(IFruitFactory fruitFactory)
   {
      _fruitFactory = fruitFactory
   }

   public string GetApple
   {
      return  _fruitFactory.CreateApple().ToString();
   }
}

现在我的问题是:有没有一个很好的方法来做到这一点,而无需创建工厂?例如,我可以以某种方式使用像Ninject这样的依赖注入器吗?

5 个答案:

答案 0 :(得分:1)

您可以模拟已实现的类型。我所知道的产品做得非常好Typemock。微软有一个名为Moles的产品,但我还没有遇到任何实际使用它的人。我没有太多的输入它的用处。

您还可以向运行时注册COM接口以拦截对象创建以注入您自己的对象。这就是这些产品的工作原理。这些是获得所需内容的最少代码更改。

长期以来,我倾向于用工厂等方式抽出Fruit。但是像Typemock或Moles这样的工具可以在短期内让你更快。如果一切都在界面后面,那么前进的摩擦就会减少。

答案 1 :(得分:1)

也许,最简单的是,使用虚方法/属性实现自己的抽象 FruitBase 类。然后进行实现,包含真正的 Fruit 类 - FruitWrapper 。与 System.Web HttpContextBase HttpContextWrapper HttpContext 的逻辑相同。

然后,您可以轻松地模拟所有内容,而无需任何特定于供应商的黑暗魔法测试框架。

public class AppleTree
{
    private readonly Func<FruitBase> _constructor;
    public AppleTree(Func<FruitBase> constructor)
    {
        _constructor = constructor;
    }

    public string GetApple()
    {
        return new _constructor().ToString();
    }
}

您可以在构造函数中交换函数:() => new FruitMock()() => new Fruit("Apple")

答案 2 :(得分:0)

您通常会Fruit实现IFruit接口而不是工厂,而是注入该接口:

public class AppleTree
{
    private readonly IFruit _apple;

    public AppleTree(IFruit apple)
    {
        _apple = apple;
    }

    public string GetApple()
    {
        return _apple.ToString();
    }
}

通过这种实现,您可以实例化:

  1. new AppleTree(new Fruit("Apple"))或者您可以拥有像NInject或StructureMap这样的IoC容器为您做这件事
  2. new AppleTree(mockApple)其中mockApple是您为单元测试创​​建的模拟IFruit对象

答案 3 :(得分:0)

如果您无法编程到IFruit界面,那么我认为抽象工厂是可行的方法。

对于单元测试,如果您使用的是Visual Studio 2012,则可以尝试Microsoft Fakes。以下代码只是为了好玩(当然有效)。也许这对你的案子来说太过分了。

using Microsoft.QualityTools.Testing.Fakes;
using MyLib;

namespace UnitTestProject1
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            using (ShimsContext.Create())
            {
                MyLib.Fakes.ShimFruit.ConstructorString = delegate(Fruit f, string s)
                {
                    var shimFruit = new MyLib.Fakes.ShimFruit(f);
                    shimFruit.ToString = () =>
                    {
                        return "Orange";
                    };
                };
                AppleTree tree = new AppleTree();
                string expected = "Orange";
                Assert.AreEqual(expected, tree.GetApple());
            }
        }
    }
}

答案 4 :(得分:0)

请参阅Ninject.Extensions.Factory - 有了它,你有Func<T>个ctor参数,抽象工厂是在幕后生成的(la @ Nenad的+1回答)

(您还可以定义Abstract Factory界面并Ninject.Extensions.Factory实施该界面。