Techno Fattie

Josh Carroll


In a previous post I talked about using the PrincipalPermissionAttribute to achieve some last resort security that was tightly integrated into the framework.

The nice thing about this approach is that you don't have to worry about some other programmer forgetting to check security before calling a method in your API. The bad news is because of the tight integration, Unit Testing becomes a little tricker. Since there is not a security service to mock, we have to get our hands dirty and actually set the Principal so the method calls won't fail at runtime.

We got around this last time by using GenericPrincipal and GenericIdentity, two classes provided for us by the FCL writers. However, the added code distracts from the test we really want to write. Lets look at that example again.

[TestMethod]
public void ShouldGetSuperSecretFromAgentUsingGenericPrincipalAndIdentity()
{
var awesomeSauce = new GenericPrincipal(
new GenericIdentity("jbond"), new[] { "007" }
);

Thread.CurrentPrincipal = awesomeSauce;

var agent = new SecretAgent();

var secret = agent.GetSuperSecretStuff();

Assert.IsNotNull(secret);
}

So this isn't terrible right now, but it is code that will have to be repeated over and over again for each test that calls that method. In this scenario we really aren't interested in testing the security, but rather the guts of the method. We want to test security, but not in this method. Ok, let's start DRYing up this code.

The easiest and simplest choice would be to just refactor out the code into a method, and then call it before our test. This is simple enough to do.

[TestMethod]
public void ShouldGetSuperSecretFromAgentUsingGenericPrincipalAndIdentity()
{
SetUserRoles("007");

var agent = new SecretAgent();

var secret = agent.GetSuperSecretStuff();

Assert.IsNotNull(secret);
}

private void SetUserRoles(params String[] roles)
{
if(roles == null || roles.Length == 0) return;

var principal = new GenericPrincipal(
new GenericIdentity("jbond"), roles
);

Thread.CurrentPrincipal = principal;
}

That is slightly better, and gives a much better indication of what is going on when looking at the test, but I still don't like it for a couple of reasons:

  • The call to SetUserRoles still takes away from the purpose of the test. Remember this test isn't about security, but the behavior of the method itself. Everytime you look at this test you will first have to figure out why the security is there, and this increases friction and slows down the RED/GREEN/REFACTOR cycle.
  • Code is still repeated because everytime we test a path through any method with security we will have to make a call to SetUserRoles, and that sucks. Any code that can be copied and pasted is worth your time to refactor.

I really want to get to not thinking about security when I write my tests, so let's see if we can at least do that. Those of you familiar with any of the major Unit Testing Frameworks will know that they all have an attribute that designates one method that will run before every test is run. In MSTest it is the TestInitializeAttribute. We can use that to neatly tuck our security code away into a single method so we don't have to repeat it for every test.


[TestInitialize]
public void Init()
{
SetUserRoles("007");
}

[TestMethod]
public void ShouldGetSuperSecretFromAgentUsingGenericPrincipalAndIdentity()
{
var agent = new SecretAgent();

var secret = agent.GetSuperSecretStuff();

Assert.IsNotNull(secret);
}

//SetUserRoles Method


Now, this is certainly a lot better than the previous version since we only have to call SetUserRoles in one place. Also, our test method is free from the security clutter so we don't have any mental road blocks while reading the test. However, I am still not very happy with the end result.

Why?

Remember when I said I didn't want to have to think about the security while writing my tests? Well... I still do. Everytime I write a new method in my SUT, I will no doubt add another security attribute to it. Or, I will write a test to cover security, and then add the attribute to make my test pass. Either way, I will have to come back to the Init method and add another role to the list.

Nothing will cripple your unit testing efforts faster than brittle, hard to maintain tests!

So how do we get around this? Ok so I already told you in Part 1 that we could build a custom Principal and Identity in order to achieve the desired results, but I wanted to expound a little on the rationale for actually doing it.

In Part 3 we solve all our problems (I promise!)



comments powered by Disqus