Simplest way to mock properties of PostSharp attribute
Asked Answered
R

1

7

I'm using a PostSharp method attribute to do authorisation and auditing on my WCF service. It's working properly but now I'm trying to get my unit tests working with the attribute and am struggling to find a way to mock and inject the properties on the attribute.

My attribute is as below.

[Serializable]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class AuthoriseAndAuditAttribute : OnMethodBoundaryAspect
{
    private static ILog logger = AppState.logger;

    private static Ninject.IKernel _kernel = MyKernel.Kernel;

    private UserRoleTypesEnum _requiredRole = UserRoleTypesEnum.None;

    [Inject]
    public IServiceAuthToken _serviceAuthToken { get; set; }

    [Inject]
    public UserSessionDataLayer _userSessionDataLayer { get; set; }

    public AuthoriseAndAuditAttribute(UserRoleTypesEnum role = UserRoleTypesEnum.None)
    {
        _requiredRole = role;
        _kernel.Inject(this);
    }

    public override void OnEntry(MethodExecutionArgs args)
    {
        // Get the user's session from cookie.
        UserSession userSession = GetUserSession();

        // Check that user is in the required role.
        bool isAuthorised = (_requiredRole == UserRoleTypesEnum.None || (userSession != null && userSession.Roles.Contains(_requiredRole)));

        if (!isAuthorised)
        {
            logger.Warn("Not authorised for " + args.Method.Name + ".");
            throw new UnauthorizedAccessException();
        }
        else if (userSession != null)
        {
            Thread.CurrentPrincipal = new MyPrincipal(userSession);
        }
    }

    private UserSession GetUserSession()
    {
        if (_serviceAuthToken != null)
        {
            string sessionID = _serviceAuthToken.GetSessionID();

             if (!sessionID.IsNullOrBlank())
             {
                 return _userSessionDataLayer.GetForSessionID(sessionID);
             }
         }

         return null;
     }
}

I have a singleton class setting up the Ninject kernel:

public class MyKernel
{
    public static StandardKernel Kernel { get; set; }

    static MyKernel()
    {
        Kernel = new StandardKernel();
        Kernel.Bind<IServiceAuthToken>().To<ServiceAuthToken>();
        Kernel.Bind<UserSessionDataLayer>().To<UserSessionDataLayer>();
    }
}

In my WCF service I use the PostSharp attribute as below:

[AuthoriseAndAudit(UserRoleTypesEnum.Operator)]
public JSONResult<bool> IsAliveAuthorised()
{
   return new JSONResult<bool>() { Success = true, Result = true };
}

And in my unit test I'm using RhinoMocks to try and mock the two DI properties in the attribute.

 [TestMethod]
 public void IsAliveAuthorisedIsAuthorisedTest()
 {
     var mockServiceAuthToken = MockRepository.GenerateStrictMock<ServiceAuthToken>();
     mockServiceAuthToken.Stub(x => x.GetSessionID()).Return("x");
     var mockUserSessionDataLayer = MockRepository.GenerateStrictMock<UserSessionDataLayer>();
     mockUserSessionDataLayer.Stub(x => x.GetForSessionID(Arg<string>.Is.Anything)).Return(new UserSession());

     MyKernel.Kernel.Bind<ServiceAuthToken>().ToConstant(mockServiceAuthToken);
     MyKernel.Kernel.Bind<UserSessionDataLayer>().ToConstant(mockUserSessionDataLayer);

     var service = new MyService();
     Assert.IsTrue(service.IsAliveAuthorised().Result);
}

The issue I have is the mock objects in the unit test are never ending up being set as the properties on the attribute. What am I doing wrong or conversely is there a better way to do unit testing on a PostSharp attribute? Also bearing in mind I really want to minimise the use of the Ninject DI to the bare minimum.

Romonaromonda answered 19/1, 2012 at 4:18 Comment(10)
So what is assigned to the properties? The actual objects, or are they null?Freestanding
No they are not null. The _kernel.Inject(this) call is what sets the two property objects. What I'm trying to do with my unit test is change the objects that the ninject kernel uses to set them with and that's the bit that's not working.Romonaromonda
Right, so you are expecting the Mock objects to be there, but instead it's the "real" objects that are still showing up?Freestanding
Yep that's it, although I was "hoping" rather than "expecting" :).Romonaromonda
Heh, okay. I was able to reproduce this too, but I don't know if this is a Ninject issue or a PostSharp one (or both!). I'll keep working on it...Freestanding
Is Ninject a critical part of your project or just used for unit testing purposes?Eula
Have you looked at autofixture.codeplex.com ?Viator
No I hadn't come across autofixture but I don't think it will help in this case. I can create my mock objects ok, it's getting them into an attribute class that's the problem.Romonaromonda
Based on this documentation here - doc.sharpcrafters.com/postsharp-2.1/… - I don't think Aspect constructors are being called at runtime. However, I moved the _kernel.Inject(this) into RuntimeInitialize, and it still doesn't work. We (Postsharp MVPs) are working on it, though! We're all very interested in figuring out how to do this too :)Freestanding
@sipwiz, have you tried my answer below? That should work.Openfaced
O
1

Instead of using the [Inject] attribute on your properties, redefine them like this:

    public IServiceAuthToken _serviceAuthToken { get { return _kernel.Get<IServiceAuthToken>(); } }

    public UserSessionDataLayer _userSessionDataLayer { get { return _kernel.Get<UserSessionDataLayer>(); } }

Also, in your test method you need to re-bind (note also that you were using the concrete type ServiceAuthToken in the first bind instead of the interface IServiceAuthToken):

MyKernel.Kernel.Rebind<IServiceAuthToken>().ToConstant(mockServiceAuthToken);
MyKernel.Kernel.Rebind<UserSessionDataLayer>().ToConstant(mockUserSessionDataLayer);
Openfaced answered 7/2, 2012 at 2:5 Comment(1)
That's very close to what I ended up doing. The only difference being instead of calling the kernel.Get on each property I call kernel.Inject at the start of the PostSharp OnEntry method. And I also do tehe kernel.Rebind in my unit test initialisation.Romonaromonda

© 2022 - 2024 — McMap. All rights reserved.