So last time, I showed you how to do something before a method is called. That’s nice but what if you want to do something after it’s called?
Let’s look again at the code under test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
1: public class UserManager 2: { 3: string prefix = "pre"; 4: string suffix = "suff"; 5: 6: static public int LogInCount = 0; 7: public bool CanUserLogIn(string user) 8: { 9: return Authenticate(prefix, user, suffix); 10: } 11: 12: private bool Authenticate(string prefix, string user, string suffix) 13: { 14: string fullUserName = prefix + user + suffix; 15: return Authenticate(fullUserName); 16: } 17: 18: private bool Authenticate(string fullUserName) 19: { 20: bool result = Authenticator.IsUserAuthorized(fullUserName); 21: if (result) 22: LogInCount++; 23: return result; 24: } 25: } |
As you probably can guess, it’s the part that tells Isolator to run the original code. We didn’t call it in the future tense, though. The method will be executed after the code in the DoInstead clause is completed.
In our next case, we want the Authenticate overload with 1 parameter to also run. But we want to log (and maybe even assert) the static variable LogInCounter.
Now, we need a way to tell Isolator to invoke the method, and continue running. Let’s take a look at this test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
1: [TestMethod] 2: public void CanUserLogIn_2ndAuthenticateCalls_LoggingBeforeAndAfter() 3: { 4: UserManager manager = new UserManager(); 5: 6: Isolate.NonPublic.WhenCalled(manager, "Authenticate").DoInstead( 7: callContext => 8: { 9: if (callContext.Parameters.Length == 3) 10: { 11: callContext.WillCallOriginal(); 12: } 13: if (callContext.Parameters.Length == 1) 14: { 15: Console.WriteLine(@"LogInCount Before : {0}", UserManager.LogInCount); 16: callContext.Method.Invoke(callContext.Instance, new object[] { callContext.Parameters[0] }); 17: Console.WriteLine(@"LogInCount After: {0}", UserManager.LogInCount); 18: } 19: return true; 20: }); 21: 22: Assert.IsTrue(manager.CanUserLogIn("Valid")); 23: } |
We see that if we call the Authenticate with 3 arguments, it will be executed once the DoInstead clause completes.
However, when the overload with 1 argument we use another option. CallContext.Method contains the metadata of the method that’s being called. With that metadata, we can invoke it before the DoInstead completes. And we can do something after the method.
We know that the definitive AOP example is logging. Is it useful to more than that in a testing context?
Imagine that the Authenticator.IsUserAuthorized method might crash the system sometime. Or leave the system unclean. For our purpose, we want to run it as-is and not mock it, but when it crashes we don’t want to ruin it for the other tests.
In this case we can do something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
1: [TestMethod] 2: public void CanUserLogIn_AuthenticatorCalled_WithCleanUp() 3: { 4: UserManager manager = new UserManager(); 5: 6: Isolate.WhenCalled(() => Authenticator.IsUserAuthorized("")). 7: DoInstead((callContext) => 8: { 9: try 10: { 11: callContext.Method.Invoke(null, new object[] { callContext.Parameters[0] }); 12: } 13: catch (Exception e) 14: { 15: // do some error handling 16: } 17: finally 18: { 19: // do some clean up 20: } 21: return true; 22: 23: }); 24: Assert.IsTrue(manager.CanUserLogIn("Valid")); 25: } |
When the method IsUserAuthorized is called, we run it inside a try-catch-finally block, which allows us to the recovery and clean up we need when going to the next test.
What other AOP functionalities did you ever wished you had while testing?