Aspects are believed to be difficult to test mainly because they are statically scoped by default, that is, aspect objects are stored in static fields. However, any aspect can be made instance-scoped if it implements the IInstanceScopedAspect interface. See Understanding Aspect Lifetime and Scope for more information about aspect scopes.
Instance-scoped aspects can consume dependencies from the objects to which they are applied. They can also add dependencies to the target objects.
For instance, an aspect can consume a service ILogger using the following procedure.
Consuming a service from an instance-scoped aspect
Add a public property named
Loggerof typeILoggerto the aspect, and add the IntroduceMemberAttribute custom attribute. The aspect then adds a property to the target class. Set itsOverrideActionproperty toMemberOverrideAction.Ignoreto ignore the property if it already exists in the target type or if another aspect has added it.Add the custom attributes ImportAttribute and CopyCustomAttributesAttribute to the
Loggerproperty. The aspect then adds the[Import]custom attribute to theLoggerproperty that it adds to the target class.Add a public field named
LoggerPropertyof typeProperty<ILogger>to the aspect class, and add the ImportMemberAttribute custom attribute to this field, with"Logger"as the parameter value. The aspect can then read theLoggerproperty even if it is defined outside the aspect.The aspect can now consume the dependency by calling
this.LoggerProperty.Get().
The next example illustrates this procedure.
Example: testable logging aspect that consumes the dependency from the target object
The following code snippet shows a logging aspect and how it could be used in production code:
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.ComponentModel.Design;
using System.Reflection;
using PostSharp.Aspects;
using PostSharp.Aspects.Advices;
using PostSharp.Extensibility;
using PostSharp.Reflection;
using PostSharp.Serialization;
namespace DependencyResolution.InstanceScoped
{
public interface ILogger
{
void Log(string message);
}
[PSerializable]
public class LogAspect : OnMethodBoundaryAspect, IInstanceScopedAspect
{
[IntroduceMember(Visibility = Visibility.Family, OverrideAction = MemberOverrideAction.Ignore)]
[CopyCustomAttributes(typeof (ImportAttribute))]
[Import(typeof(ILogger))]
public ILogger Logger { get; set; }
[ImportMember("Logger", IsRequired = true)]
public Property<ILogger> LoggerProperty;
public override void OnEntry(MethodExecutionArgs args)
{
this.LoggerProperty.Get().Log("OnEntry");
}
object IInstanceScopedAspect.CreateInstance(AdviceArgs adviceArgs)
{
return this.MemberwiseClone();
}
void IInstanceScopedAspect.RuntimeInitializeInstance()
{
}
}
[Export(typeof (MyServiceImpl))]
internal class MyServiceImpl
{
[LogAspect]
public void LoggedMethod()
{
Console.WriteLine("Hello, world.");
}
}
internal class Program
{
private static void Main(string[] args)
{
AssemblyCatalog catalog = new AssemblyCatalog(typeof (Program).Assembly);
CompositionContainer container = new CompositionContainer(catalog);
MyServiceImpl service = container.GetExport<MyServiceImpl>().Value;
service.LoggedMethod();
}
}
[Export(typeof (ILogger))]
internal class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
}
The following code snippet shows how the logging aspect can be tested:
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace DependencyResolution.InstanceScoped.Test
{
[TestClass]
public class TestLogAspect
{
[TestMethod]
public void TestMethod()
{
TypeCatalog catalog = new TypeCatalog(typeof (TestLogger), typeof (TestImpl));
CompositionContainer container = new CompositionContainer(catalog);
TestImpl service = container.GetExport<TestImpl>().Value;
TestLogger.Clear();
service.TargetMethod();
Assert.AreEqual("OnEntry" + Environment.NewLine, TestLogger.GetLog());
}
[Export(typeof (TestImpl))]
private class TestImpl
{
[LogAspect]
public void TargetMethod()
{
}
}
}
[Export(typeof (ILogger))]
internal class TestLogger : ILogger
{
public static readonly StringBuilder stringBuilder = new StringBuilder();
public void Log(string message)
{
stringBuilder.AppendLine(message);
}
public static string GetLog()
{
return stringBuilder.ToString();
}
public static void Clear()
{
stringBuilder.Clear();
}
}
}