3

I have an aspect sorting list.

public override void OnInvoke(MethodInterceptionArgs args)
{
    args.Proceed();
    var string_list = (args.ReturnValue as List<string>);
    string_list.Sort();

    Console.WriteLine("Postsharp Aspect içerisinde sıralanan koleksiyon: ");
    foreach (var item in string_list)
    {
      Console.WriteLine(item);
    }
}

I have a method returning list.

    [PostSharpExecuteAfterMethod]
    public virtual List<string> Liste_Döndür()
    {
        List<string> liste = new List<string>();
        liste.Add("g");
        liste.Add("b");
        liste.Add("hjh");
        liste.Add("a");

        Console.WriteLine("Method'dan dönen string liste: ");
        foreach (var item in liste)
        {
            Console.WriteLine(item);
        }

        return liste;
    }

This is my test method.

  public class SomeClass
  {
    [PostSharpExecuteAfterMethod]
    public virtual List<string> GimmeSomeData()
    {
        throw new NotImplementedException();
    }
  }

[TestClass]
public class UnitTest1
{
    [TestMethod]
    //[PostSharpExecuteAfterMethod]
    public void TestMethod1()
    {
        var mock = new Mock<SomeClass>();
        mock.Setup(m => m.GimmeSomeData()).Returns(() => new List<string> { "1", "2", "3" });
        //liste
        var resultList = mock.Object.GimmeSomeData();
    }
}

So, I want to call my aspect in my test method with using Moq. Whenever i try to create a mocked class or something. It didn't work. How can I do that?

ImC0der
  • 308
  • 2
  • 18

1 Answers1

2

The aspect in your code sample is not invoked due to the ordering of the interceptions on the target method. The mocking interceptor is added before the PostSharp interceptor, and so it returns the result before the PostSharp aspect has a chance to execute.

In most cases this is the desired behavior. You can think of an aspect as an additional code within your method. Therefore your own code and applied aspects should be tested as a single unit.

If your use case requires you to separate the aspect and the original method body during testing then you need to ensure that the mocking interceptor is invoked after the PostSharp aspect. Below you can find a sample MockAspect that allows you to achieve this:

public interface IMockable<T> where T : class
{
    Mock<T> CreateMock();
}

[Conditional( "DEBUG" )] // Exclude mocking interceptor in release builds.
public class MockAspect : TypeLevelAspect, IAspectProvider
{
    public IEnumerable<AspectInstance> ProvideAspects( object targetElement )
    {
        Type targetType = (Type) targetElement;
        yield return new AspectInstance( targetElement, (IAspect) Activator.CreateInstance( typeof( MockAspectImpl<> ).MakeGenericType( targetType ) ) );
    }
}

[PSerializable]
// Make sure we are ordered after our aspect PostSharpExecuteAfterMethod.
[AspectTypeDependency(AspectDependencyAction.Order, AspectDependencyPosition.After, typeof( PostSharpExecuteAfterMethod ))]
public class MockAspectImpl<T> : IInstanceScopedAspect, IAdviceProvider, IMockable<T> where T : class
{
    [PNonSerialized]
    private Mock<T> mock;

    public object CreateInstance( AdviceArgs adviceArgs )
    {
        return new MockAspectImpl<T>();
    }

    public void RuntimeInitializeInstance()
    {
    }

    public IEnumerable<AdviceInstance> ProvideAdvices( object targetElement )
    {
        yield return new IntroduceInterfaceAdviceInstance( typeof( IMockable<T> ) );
    }

    Mock<T> IMockable<T>.CreateMock()
    {
        this.mock = new Mock<T>();
        return this.mock;
    }

    [OnMethodInvokeAdvice]
    [MulticastPointcut( Targets = MulticastTargets.Method,
                        Attributes = MulticastAttributes.Instance | MulticastAttributes.Public | MulticastAttributes.Virtual )]
    public void OnMethodInvoke( MethodInterceptionArgs args )
    {
        if ( this.mock != null )
        {
            args.ReturnValue = args.Method.Invoke( mock.Object, args.Arguments.ToArray() );
        }
        else
        {
            args.Proceed();
        }
    }
}

Next you can apply the MockAspect to your target class:

[MockAspect]
public class SomeClass
{
    [PostSharpExecuteAfterMethod]
    public virtual List<string> GimmeSomeData()
    {
        throw new NotImplementedException();
    }
}

And this is how you can use mocks that are invoked after the aspect:

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        var obj = new SomeClass();
        var mock = ( (IMockable<SomeClass>) obj ).CreateMock();
        mock.Setup( m => m.GimmeSomeData() ).Returns( () => new List<string> { "3", "1", "2" } );

        var resultList = obj.GimmeSomeData();

        Console.WriteLine( "Result: {0}", string.Join( ", ", resultList ) );
    }
}
AlexD
  • 5,011
  • 2
  • 23
  • 34