chevron-thin-right chevron-thin-left brand cancel-circle search youtube-icon google-plus-icon linkedin-icon facebook-icon twitter-icon toolbox download check linkedin phone twitter-old google-plus facebook profile-male chat calendar profile-male
Welcome to Typemock Community! Here you can ask and receive answers from other community members. If you liked or disliked an answer or thread: react with an up- or downvote.
0 votes
Hi!
I'm experiencing problems with faking callback delegate.
Here are simplified example (in real world Callee.CallMe is async that's why I need callback) and test:

   public class Caller
   {
      public Callee Callee { get; set; }
      public MyClass Prop { get; set; }

      public void Call()
      {
         Callee.CallMe(para => this.Refresh(para));
      }

      private void Refresh(MyClass para)
      {
         this.Prop.ClearProp();
      }
   }

   public class Callee
   {
      public void CallMe(Action<MyClass> callBack)
      {
         // here is some internal logic which I don't want to test and mock
         callBack(new MyClass());
      }
   }

   public class MyClass
   {
      private int size = 1;

      public void ClearProp()
      {
         this.size = 0;
      }
   }

   [TestClass]
   [Isolated]
   public class UnitTest1
   {
      [TestMethod]
      public void Test()
      {
         MyClass cached = new MyClass();

         Caller caller = new Caller();
         Callee callee = Isolate.Fake.Instance<Callee>();
         MyClass cl = new MyClass();
         Isolate.WhenCalled(() => caller.Callee).WillReturn(callee);
         Isolate.WhenCalled(() => caller.Prop).WillReturn(cached);

         Isolate.WhenCalled(() => callee.CallMe(null)).DoInstead(context => 
            {
               ((Action<MyClass>)context.Parameters[0])(cl);
            });

         caller.Call();
      }
   }


The problem is that when Refresh(MyClass para) is called the Caller.Prop is null
I tried this test in 6.0.4 and 6.0.5.(0?)
Any suggestions? Thanks
asked by 3ter (2.5k points)

9 Answers

0 votes
Hello? Any experts or developers? =] I'm really stuck. May be there is another way to test calling method with a callback delegate as a parameter?
answered by 3ter (2.5k points)
0 votes
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.
answered by tillig (6.7k points)
0 votes
Thanks for the reply Travis.

Yes, the parameter in the Refresh() method is ignored and it's not really needed in this repro, I haven't cleaned it up from the real code. Sorry for being confusing.

As for mocking as little as possible - probably you're right, though my rule of thumb is to mock everything you don't want to test, it's probably a point for a discussion but not here =] In this repro you can instantiate Caller and initialize it's properties but in my code it's impossible because it's actually a Controller in MVC with DI and whole bunch of other patterns and I don't really need to summon all this magic in this test =]

Summarizing all that said, here is the most simplified repro. The question is when Refresh() is called why this.Prop == null though it's mocked

   public class Caller
   {
      public Callee Callee { get; set; }
      public MyClass Prop { get; set; }

      public void Call()
      {
         Callee.CallMe(() => this.Refresh());
      }

      private void Refresh()
      {
         // And here this.Prop is null though I mocked it
         this.Prop.ClearProp();
      }
   }

   public class MyClass
   {
      public void ClearProp() { }
   }
   
   public class Callee
   {
      public void CallMe(Action callBack)
      {
         // Here is async call to WCF service and I don't want to test it now
         // ...
         
         // and finally callback
         callBack();
      }
   }


   [TestClass]
   [Isolated]
   public class UnitTest1
   {
      [TestMethod]
      public void Test()
      {
         MyClass cached = new MyClass();
         Callee callee = new Callee();      

         // I have to mock this class in real life. Can't do nothing with it =]
         Caller caller = Isolate.Fake.Instance<Caller>(Members.CallOriginal);
         Isolate.WhenCalled(() => caller.Callee).WillReturn(callee);
         // Here I mock Prop property
         Isolate.WhenCalled(() => caller.Prop).WillReturn(cached);

         // Swap Callee.CallMe to only call the delegate passed as a parameter
         Isolate.WhenCalled(() => callee.CallMe(null)).DoInstead(context =>
            {
               ((Action)context.Parameters[0])();
            });

         caller.Call();
      }
   }
answered by 3ter (2.5k points)
0 votes
[Deleted an earlier post I had made as I was misreading the code. I think the naming here with Caller, Callee, CallMe, etc. is sort of confusing and hard to visualize for me. Sorry if you caught that earlier post and it confused you.]
answered by tillig (6.7k points)
0 votes
OK, I stepped through it and it appears you have found an Isolator defect.

I added an "instance ID" property to each of the objects really quick...
public Guid __instanceId = Guid.NewGuid();

...so I could follow through and make sure the various calls and such were acting on exactly the same instances as it appears (things can sometimes get a little tricky when you're not just dealing with POCOs) and everything there looks correct. The issue appears to be only once the lambda gets called - the "this" reference (to the Caller object) isn't triggering the mocks to occur.

I fired up the Typemock Isolator Tracer and I did see one weird thing: If you look at the Caller object, you'll see the tracer notes TWO instances of Caller in its expectations. One is an unexpected call to a Caller constructor; the other is the instance where Callee and Prop are both [successfully] mocked.

I don't know if this is how the AAA syntax handles separate mocking calls or what. Things have gotten a little confusing to troubleshoot since AAA was introduced.

In any case, you have what I believe to be an Isolator defect where it's not properly handling mocking across closures (the "this" reference being part of the closure for the lambda).

If I were you, I'd email support and include a reference to this thread. They're pretty quick about getting a fix out once they've figured out the problem.

One side note/tip to get better help faster: You might want to pick an easier-to-follow naming scheme for your repro objects. Even if it's "Car," "Boat," "Dog" or whatever, it was just a little hard to follow what you were trying to do with the current naming scheme. No big deal, just something to consider.

Sorry I couldn't be more help. :(
answered by tillig (6.7k points)
0 votes
Hi,

I think that the problem is actually an old known limitation.
I thought that it was solved some time ago, but I'm not sure if it actually was and if yes on what version.

the limitation is that inside DoInstead and other similar mechanisms.
mocking is turned off.
In your example since the call to refresh is done inside the code activated from the DoInstead, the call is not faked but go to the real implementation.

We will need some response from Typemock support to understand the status of this issue.
answered by error (6.6k points)
0 votes
Thanks guys.

I'll contact support, hope they will clear things up.
Travis, I'm sorry for unnecessary complexity in the example. I'll try to be more clear in future =]
answered by 3ter (2.5k points)
0 votes
Hello,

I'm terribly sorry for not answering this sooner. I just wanted to let you know that we got your support email, and I will contact you shortly regarding this issue.

Thanks!
answered by igal (5.7k points)
0 votes
Just a quick update,

The issue is correctly identified by Lior - it's caused by a limitation of executing fakes in DoInstead, however we have fixed this limitation in the upcoming release of Isolator v6.0.6, which should be public within the next few days!

We'll publish the complete changelog and new features on our blog, but for now let's just say that DoInstead can handle fake objects, provided they are not dynamic return.
answered by igal (5.7k points)
...