如何替换 MEF 容器中导出的零件/对象?

发布于 2024-09-25 23:55:00 字数 1080 浏览 0 评论 0原文

我正在运行一个 WPF 应用程序,它需要影响 UI 的所有操作都在 UI 线程上。 WPF 还提供了一个 Dispatcher 类来处理此问题 - 因此我将其提取到依赖项中。

public interface UIActionExecutor
    {
        void Do(Action action);
    }

因此,在我的生产代码中,我使用委托给 WPF 调度程序的导出实现。我使用 MEF 进行 DI。

现在的问题是,在我的验收测试中,我需要用 Mock 替换容器中响应 UIActionExecutor 的部分/对象。因此,我需要从容器中删除 ExecutorUsingWpfDispatcher 并在其位置添加 MockUIActionExecutor 。这听起来很简单(如果我没有使用 MEF)...但是我的搜索技能并没有帮助我找到如何使用 MEF 容器执行此操作的答案?

更新: 如果有人想知道该解决方案的工作原理/原因 - 请阅读 Glenn Block 的博客文章#2。这就是我最终使用的

    var defaultExportProvider = new CatalogExportProvider(__defaultCatalog);
    var catalogOfMocks = new AssemblyCatalog(assemblyExportingMocks);
    // order of params important (precedence left to right)
    __container = new CompositionContainer(catalogOfMocks, defaultExportProvider);
    defaultExportProvider.SourceProvider = __container

I have a WPF app running, which needs all operations that affect the UI to be on the UI Thread. WPF also provides a Dispatcher class that handles this - so I extracted that into a dependency.

public interface UIActionExecutor
    {
        void Do(Action action);
    }

So in my production code, I use an exported implementation which delegates to the WPF Dispatcher. I'm using MEF for DI.

Now the problem, in my acceptance tests, I need to replace the part / object in the container that responds to UIActionExecutor by a Mock. So I need to remove ExecutorUsingWpfDispatcher from my container and add MockUIActionExecutor in its place. This sounds pretty simple (if I was not using MEF)... but my searching skills haven't helped me find an answer as to how to do this with the MEF container ?

Update:
If anyone wants to know why/how the solution works - read Glenn Block's blog post#2. This is what I ended up using

    var defaultExportProvider = new CatalogExportProvider(__defaultCatalog);
    var catalogOfMocks = new AssemblyCatalog(assemblyExportingMocks);
    // order of params important (precedence left to right)
    __container = new CompositionContainer(catalogOfMocks, defaultExportProvider);
    defaultExportProvider.SourceProvider = __container

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

只有一腔孤勇 2024-10-02 23:55:00

DI 容器负责将所有内容连接在一起。

单元测试负责单独测试单个代码单元。模拟用于替换依赖项。因此原则上 DI 容器不应该在单元测试中使用。它与“单元测试”的定义相矛盾。(1)

但是,我当然可以理解,除了单元测试之外,您可能还希望进行自动化集成测试,并且您可能希望使用 MEF 但在此类测试中替换某些 MEF 部分。您可以这样做:

// first set up the main export provider
var mainCatalog = new AggregateCatalog(...); 
var mainExportProvider = new CatalogExportProvider(mainCatalog);

// now set up a container with mocks
var mockContainer = new CompositionContainer();
mockContainer.ComposeExportedValue<IFoo>(fooMock);
mockContainer.ComposeExportedValue<IBar>(barMock);

// use this testContainer, it will give precedence to mocks when available
var testContainer = new CompositionContainer(mockContainer, mainExportProvider);

// link back to the testContainer so mainExportProvider picks up the mocks
mainExportProvider.SourceProvider = testContainer;

(1)从您的博客来看,我确信您已经知道这一点。但其他人也会阅读这个答案,所以我想澄清术语“单元测试”。

A DI container is responsible for wiring everything together.

A unit test is responsible for testing a single unit of code in isolation. Mocks are used to replace dependencies. So in principle a DI container should not be used in a unit test. It contradicts the definition of "unit test".(¹)

However, I can certainly understand that you might want to do automated integration tests in addition to unit tests, and you might want to use MEF yet replace certain MEF parts in such a test. You can do that like this:

// first set up the main export provider
var mainCatalog = new AggregateCatalog(...); 
var mainExportProvider = new CatalogExportProvider(mainCatalog);

// now set up a container with mocks
var mockContainer = new CompositionContainer();
mockContainer.ComposeExportedValue<IFoo>(fooMock);
mockContainer.ComposeExportedValue<IBar>(barMock);

// use this testContainer, it will give precedence to mocks when available
var testContainer = new CompositionContainer(mockContainer, mainExportProvider);

// link back to the testContainer so mainExportProvider picks up the mocks
mainExportProvider.SourceProvider = testContainer;

(¹)Judging from your blog, I'm sure you already know this. But others will also read this answer, so I wanted to be clear about the term "unit test".

魔法少女 2024-10-02 23:55:00

无法让接受的解决方案发挥作用。下面的代码应该可以工作,并且 AggregateExportProvider 的文档中描述了优先级。

var mainContainer = new CompostionContainer();
mainContainer.ComposeExportedValue<IFoo>(new Foo() {Test = 1});

var mockContainer = new CompositionContainer();            
mockContainer.ComposeExportedValue<IFoo>(new Foo() {Test = 2});

var aggregateExportProvider = new AggregateExportProvider(
     mockContainer,   // IFoo in this container takes precedence
     mainContainer);

IFoo foo = aggregateExportProvider.GetExportedValue<IFoo>();

Console.WriteLine(foo.Test); // Outputs: 2

Could not get the accepted solution to work. Below code should work and precedence is described in the documentation for AggregateExportProvider.

var mainContainer = new CompostionContainer();
mainContainer.ComposeExportedValue<IFoo>(new Foo() {Test = 1});

var mockContainer = new CompositionContainer();            
mockContainer.ComposeExportedValue<IFoo>(new Foo() {Test = 2});

var aggregateExportProvider = new AggregateExportProvider(
     mockContainer,   // IFoo in this container takes precedence
     mainContainer);

IFoo foo = aggregateExportProvider.GetExportedValue<IFoo>();

Console.WriteLine(foo.Test); // Outputs: 2
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文