Ninject内核绑定覆盖

时间:2009-08-11 16:05:08

标签: c# unit-testing inversion-of-control ninject

我只是想知道在内核中重新绑定绑定的最佳做法是什么。

我有一个带有内核的类和一个带有默认生产绑定的私有类模块。

对于测试,我想覆盖这些绑定,以便我可以交换我的Test Doubles / Mocks对象。

确实

MyClass.Kernel.Load(new InlineModule(m=> m.Bind<IDepend>().To<TestDoubleDepend>()))

覆盖IDepend的所有现有绑定?

6 个答案:

答案 0 :(得分:5)

我尝试尽可能少地在我的代码中直接使用DI内核,而是依赖于构造函数注入(或者在特定情况下的属性,例如Attribute类)。但是,我必须使用抽象层,以便我可以设置DI内核对象,使其在单元测试中可以模拟。

例如:

public interface IDependencyResolver : IDisposable
{
    T GetImplementationOf<T>();
}

public static class DependencyResolver
{
    private static IDependencyResolver s_resolver;

    public static T GetImplementationOf<T>()
    {
        return s_resolver.GetImplementationOf<T>();
    }

    public static void RegisterResolver( IDependencyResolver resolver )
    {
        s_resolver = resolver;
    }

    public static void DisposeResolver()
    {
        s_resolver.Dispose();
    }
}

使用这样的模式,您可以通过使用模拟或伪实现调用IDependencyResolver来设置单元测试中的RegisterResolver,该实现返回您想要的任何对象,而无需连接完整模块。如果您将来选择切换到另一个容器,它还具有从特定IoC容器中抽象代码的第二个好处。

当然,您还需要根据需要向IDependencyResolver添加其他方法,我只是将这里的基础知识作为示例。是的,这将要求您在Ninject内核周围编写一个超级简单的包装器,它也实现了IDependencyResolver

你想要这样做的原因是你的单元测试应该只测试一件事,并且通过使用你的实际IoC容器,你实际上比一个被测试的类更多地运动,这可能导致错误的否定让你的测试变得脆弱,并且(更重要的是)让开发人员对它们的准确性有一定的信心。这可能导致测试冷漠和放弃,因为测试失败但软件仍然可以正常工作(“不用担心,一个总是失败,这不是什么大问题”)。

答案 1 :(得分:3)

我只是希望这样的作品

        var kernel = new StandardKernel(new ProductionModule(config));
        kernel.Rebind<ITimer>().To<TimerImpl>().InSingletonScope();

其中ProductionModule是我的生产绑定,我通过在特定测试用例中调用Rebind来覆盖。我在重新绑定的几件物品上打电话重新绑定。

ADVANTAGE:如果有人向生产模块添加了新的绑定,我会继承它们,所以它不会以这种方式破坏,这可能很好。这一切都适用于Java中的Guice ...希望它也可以在这里工作。

答案 2 :(得分:1)

我倾向于做一个单独的测试项目,完成它自己的绑定 - 我当然假设我们正在谈论某种单元测试。测试项目使用自己的内核并将测试项目中的模块加载到该内核中。项目中的测试在CI构建期间执行,并通过构建脚本执行完整构建,但测试从未部署到生产中。

我意识到您的项目/解决方案设置可能不允许这种组织,但从我看到的情况来看似乎很典型。

答案 3 :(得分:1)

Peter Mayer的方法对于单元测试很有用,但恕我直言,使用构造函数/属性注入手动注入Mock是不是更容易?

对我来说,对测试项目使用特定绑定对于其他类型的测试(集成,功能)更有用,但即使在这种情况下,您肯定需要根据测试更改绑定。

我的方法是kronhrbaugh和Hamish Smith的某种组合,创建一个“依赖性解析器”,您可以在其中注册和取消注册要使用的模块。

答案 4 :(得分:0)

我会在MyClass中添加一个接受模块的构造函数 这不会用于生产,但会用于测试 在测试代​​码中,我将传递一个模块,该模块定义了所需的测试双精度。

答案 5 :(得分:0)

对于我正在进行的项目,我为每个环境(测试,开发,阶段,生产等)创建了单独的模块。这些模块定义了绑定。

因为dev,stage和production都使用了许多相同的绑定,所以我创建了一个共同的模块。然后每个人都添加环境特定的绑定。

我还有一个KernelFactory,当传递环境令牌时,将使用适当的模块对IKernel进行假脱机。

这允许我切换我的环境令牌,这反过来会自动更改我的所有绑定。

但是如果这是用于单元测试的话,我同意上面的评论,即允许手动绑定的简单构造函数是一种方法,因为它会使Ninject远离你的测试。