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
0 votes
I have a non-public (protected) method declared as virtual. It is an overload of a public version of the method.

The methods are:
public void Dispose()
protected virtual void Dispose( bool isDisposing )

The public method calls the non-public overloaded version. I am trying to ignore the functionality within the non-public overloaded version of the method using IgnoreCall(). This doesn't work. The functionality of the non-public overloaded method executes.

I have tried many different things even changing the method signatures to see what might cause the IgnoreCall() to work. I did find that having the overloaded method declared as 'virtual' made no difference. However, if I changed the overloaded method from 'protected' to 'public' it worked as expected.

The need to isolate a non-public overloaded method isn't that obscure and I'd imagine is pretty common. In fact, I was surprised that this doesn't work at all.

Is this ability supported or am I missing something?
asked by dblack (8.4k points)

5 Answers

0 votes
Hi,

You can solve the problem by using WithExactArguments to call the original implementation of public Dispose while using the default of Isolate.Fake.Instance that will do a recursive fake on all other methods.
Here's an example:
public class Foo : IDisposable
{
    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool isDisposing)
    {
        throw new NotImplementedException();
    }
}

[Test]
public void TestDispose()
{
    var fake = Isolate.Fake.Instance<Foo>();
    Isolate.WhenCalled(() => fake.Dispose()).WithExactArguments().CallOriginal();

    using (fake)
    {
    }
}


:arrow: Usually when you want to fake non public methods you should use
Isolate.NonPublic ... API, but in this case it will not work properly because of the overloading of Dispose method.
answered by ohad (35.5k points)
0 votes
Hi Ohad,

Thank you for your response. Unfortunately, your suggested solution doesn't solve the problem.

Here is the code and unit test:

public class Foo : IDisposable 
{ 
    private bool IsDisposed { get; set; }
    public void Dispose() 
    { 
        Dispose(true); 
        GC.SuppressFinalize( this );
    } 

    protected virtual void Dispose(bool isDisposing) 
    { 
        try
        {
            if ( !this.IsDisposed )
            {
                if ( isDisposing )
                {
                    if ( this.State == CommunicationState.Opened ||
                         this.State == CommunicationState.Opening )
                    {
                        this.Close();
                    }
                    else
                    {
                        // if the client is in Faulted state abort.
                        this.Abort();
                     }
                }
            }
        }
        finally
        {
            this.IsDisposed = true;
        }
    } 
}


[TestMethod]
[Isolated]
public void IsDisposed_Property_Is_Set_To_Proper_Values()
{
    Foo fake = Isolate.Fake.Instance<Foo>( Members.CallOriginal, ConstructorWillBe.Ignored );
            
    // verify that the default value of the IsDisposed property is set to false
    Isolate.NonPublic.Property.WhenGetCalled( fake, "IsDisposed" ).WillReturn( false );

    Isolate.NonPublic.WhenCalled( fake, "Dispose" ).WithGenericArguments(typeof(bool)).IgnoreCall();
    //HACK - we have to call Abort() on the fake because IgnoreCall() doesn't ignore the code in the overloaded method
    Isolate.WhenCalled( () => fake.Abort() ).IgnoreCall();

    fake.Dispose();

    // verify that the value of the IsDisposed property is set to false
    Isolate.Verify.NonPublic.Property.WasCalledSet( fake, "IsDisposed" ).WithArgument( true );
}


I've also tried your solution:

 
[TestMethod]
[Isolated]
public void IsDisposed_Property_Is_Set_To_Proper_Values()
{
    Foo fake = Isolate.Fake.Instance<Foo>( Members.CallOriginal, ConstructorWillBe.Ignored );
            
    // verify that the default value of the IsDisposed property is set to false
    Isolate.NonPublic.Property.WhenGetCalled( fake, "IsDisposed" ).WillReturn( false );

    Isolate.WhenCalled(() => fake.Dispose()).WithExactArguments().CallOriginal(); 

    fake.Dispose();

    // verify that the value of the IsDisposed property is set to false
    Isolate.Verify.NonPublic.Property.WasCalledSet( false, "IsDisposed" ).WithArgument( true );
}
answered by dblack (8.4k points)
0 votes
Hi,

Actually the second test does work :)
You made one small mistake in the last line:
Isolate.Verify.NonPublic.Property.WasCalledSet( false, "IsDisposed" ).WithArgument( true );
WasCalledSet() method should get the fake as an argument and not 'false'.
Also the line: Isolate.WhenCalled(() => fake.Dispose()).WithExactArguments().CallOriginal();
is redundent since you set the default behavior of the class as Memebers.CallOriginal

Please try the code below and let me know if works for you:
[Test, Isolated]
public void IsDisposed_Property_Is_Set_To_Proper_Values()
{
    Foo fake = Isolate.Fake.Instance<Foo>(Members.CallOriginal);                 
    Isolate.NonPublic.Property.WhenGetCalled( fake, "IsDisposed" ).WillReturn( false );
    
    // not needed since you created the fake above with Members.CallOriginal
    //Isolate.WhenCalled(() => fake.Dispose()).WithExactArguments().CallOriginal();

    fake.Dispose();      
    Isolate.Verify.NonPublic.Property.WasCalledSet( fake, "IsDisposed" ).WithArgument( true );
}       
answered by ohad (35.5k points)
0 votes
Hi,

Actually the second test does work :)
You made one small mistake in the last line:
Isolate.Verify.NonPublic.Property.WasCalledSet( false, "IsDisposed" ).WithArgument( true );
WasCalledSet() method should get the fake as an argument and not 'false'.
Also the line: Isolate.WhenCalled(() => fake.Dispose()).WithExactArguments().CallOriginal();
is redundent since you set the default behavior of the class as Memebers.CallOriginal

Please try the code below and let me know if works for you:
[Test, Isolated]
public void IsDisposed_Property_Is_Set_To_Proper_Values()
{
    Foo fake = Isolate.Fake.Instance<Foo>(Members.CallOriginal);                 
    Isolate.NonPublic.Property.WhenGetCalled( fake, "IsDisposed" ).WillReturn( false );
    
    // not needed since you created the fake above with Members.CallOriginal
    //Isolate.WhenCalled(() => fake.Dispose()).WithExactArguments().CallOriginal();

    fake.Dispose();      
    Isolate.Verify.NonPublic.Property.WasCalledSet( fake, "IsDisposed" ).WithArgument( true );
}       


Hi Ohad,

The last line was actually a typo I made here when writing the post and not a direct copy/paste from my method.

When the test you have above executes, I get a NullReferenceException on the line:
this.Abort();

in the overloaded Dispose(bool). This is because the overloaded method is not being mocked. This code should never execute (unless I am missing something).
answered by dblack (8.4k points)
0 votes
Hi,

I think I'm missing something :(
From the test you posted it looks like you want to test the Method Dispose(bool). If you'll fake Dispose(bool) than the assert at the end of the test will always fail.
My guess is that because the constructor of the class is ignored some members are not initializes properly.
If that is the case you have two options to solve it:
1. Call the constructor and pass parameters if needed:
var fake = Isolate.Fake.Instance<Foo>(Members.CallOriginal, ConstructorWillBe.Called, arg1, arg2 ...);


2. Fake (as you actually did) the Abort method which throws the exception.

Hope I understood the test correctly this time.
answered by ohad (35.5k points)
...