Your reproduction actually has a defect in it. The swap was happening, but the issue is in the Refresh method - the delegate in the Call method passes a parameter, but the Refresh method totally ignores the parameter. As such, you either need to change the way Refresh works or you need to change what you mock to be at a different level (e.g., mock the Prop property rather than trying to switch out the delegate parameter).
Here's some updated code that works. First, your reproduction code:
public class Caller
{
public Callee Callee { get; set; }
public MyClass Prop { get; set; }
public void Call()
{
// This is the line that's actually getting mocked by the
// DoInstead call. The DoInstead is swapping in the "cl"
// parameter for the "cached" parameter (in the test).
Callee.CallMe(para => this.Refresh(para));
}
private void Refresh(MyClass para)
{
// This method was ignoring the passed-in parameter, so
// while it was receiving the DoInstead parameter, action
// wasn't happening on it.
para.ClearProp();
//this.Prop.ClearProp();
}
}
public class Callee
{
public void CallMe(Action<MyClass> callBack)
{
// This line won't be hit with the mocking in place.
callBack(new MyClass());
}
}
public class MyClass
{
private int size = 1;
public int Size
{
get
{
return size;
}
}
public void ClearProp()
{
this.size = 0;
}
}
Notice in there that I switched the Refresh method to actually pay attention to the parameter passed in. That's one alternative, and I chose that so the unit test you wrote will actually work with the DoInstead call. I also added a Size parameter to the MyClass class so we can do a test assertion to see if it's working.
The unit test changes to this:
[TestClass]
[Isolated]
public class UnitTest1
{
[TestMethod]
public void Test()
{
// Minor rearrangement for readability - all of the related
// initializations happen together. Also, using actual instances
// where possible to reduce the number of things mocked.
MyClass cached = new MyClass();
Callee callee = new Callee();
Caller caller = new Caller()
{
Prop = cached,
Callee = callee
};
// Instead of mocking the property calls, set them for real if
// possible. Did that earlier.
//Isolate.WhenCalled(() => caller.Callee).WillReturn(callee);
//Isolate.WhenCalled(() => caller.Prop).WillReturn(cached);
// This will swap the "cached" instance of MyClass for the "cl"
// instance of MyClass.
MyClass cl = new MyClass();
Isolate.WhenCalled(() => callee.CallMe(null)).DoInstead(context =>
{
((Action<MyClass>)context.Parameters[0])(cl);
});
// Run it.
caller.Call();
// Do an assertion to see if the swap actually happened.
Assert.AreEqual(0, cl.Size);
Assert.AreEqual(1, cached.Size);
}
}
A good rule of thumb is to mock as little as possible. You don't really need to mock the Callee class, so I switched that to be a concrete instance. You also don't need to mock the property getters since you can actually set the properties, so I removed that.
For a bit of readability, I rearranged things a tad at the top so related initializations all happen at once in a bit more condensed format.
Finally, I added a couple of assertions at the end so you can see if things are working. In this case, the parameter we swap in should be reset but the actual one attached to the caller object remains unchanged. Note that your mock DoInstead code didn't actually change because it was actually working.
When executed, this produces the desired results.