How to replace an exported part / object in a MEF container?
Asked Answered
W

2

5

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
Whispering answered 30/9, 2010 at 6:28 Comment(0)
A
9

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".

Autodidact answered 30/9, 2010 at 14:52 Comment(4)
This worked but didn't solve my problem - GetExportedValue<IRole> now returns the mock. However creating an object that imports IRole still uses the Real implementation. e.g. testContainer.GetExportedValue<ConsumingObject> still uses the real implementation.Whispering
And related to the 'unit test' point, yes I used the wrong word. I am actually writing an acceptance test in NUnit.Whispering
@Gishu: sorry, I had written the example from memory instead of looking at my tests were I had this working. I have edited my answer; the example should work as you expect now. The trick is to make use of the CatalogExportProvider.SourceProvider property.Autodidact
nice. It worked.. I spent a day trying to find out why - until I read Glenn Block's article.Whispering
J
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
Jenness answered 24/1, 2013 at 14:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.