I am currently trying to write some integration tests for a WCF service. The service itself calls a controller that uses ServiceSecurityContext.Current.PrimaryIdentity
to get the current user, then checks what the user can do. For the purpose of my integration tests, I wanted to hard code the current user, and then expect certain results back for that user.
Now the username lookup is nicely abstracted behind an identity provider interface, so I would ideally just use a dependency injection tool to replace the identity lookup with something that suits my tests. For a number of reasons this wasn’t possible, so instead I had to override the WCF authentication process to always return the required user.
First thing was to setup a ServiceHost
so the test runner could self-host the WCF service. You can test the service directly without hosting, but in this case we wanted to test things through a host. This implementation isn’t careful about disposing of resources properly, so I’d think twice about copying any of this for real use if I were you :-).
[TestFixture] public class MyServiceIntegrationTests { private ServiceHost host; private ServiceEndpoint serviceEndpoint; [TestFixtureSetUp] public void FixtureSetup() { host = new ServiceHost(typeof(MyService), new Uri("http://localhost:8080/TestMyService")); //Hack up host security for testing host.Authorization.ExternalAuthorizationPolicies = new List<IAuthorizationPolicy> { new TestAuthPolicy() }.AsReadOnly(); host.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.Custom; //Add endpoint serviceEndpoint = host.AddServiceEndpoint(typeof (IMyService), new BasicHttpBinding(), String.Empty); host.Open(); } [TestFixtureTearDown] public void CleanupAfterAllTests() { host.Close(); } ... }
The emphasised bit is where we rip into the WCF authorisation process, by setting the permission mode to PrincipalPermissionMode.Custom
, and substituting our own IAuthorizationPolicy
instance, TestAuthPolicy
, which looks like this:
internal class TestAuthPolicy : IAuthorizationPolicy { public string Id { get { return "TestAuthPolicy"; } } public bool Evaluate(EvaluationContext evaluationContext, ref object state) { evaluationContext.Properties["Principal"] = new TestPrincipal(); IList<IIdentity> identities = new List<IIdentity> {new TestIdentity()}; evaluationContext.Properties.Add("Identities", identities); return true; } public ClaimSet Issuer { get { return ClaimSet.System; } } }
Here we implement the Evaluate
method to update the EvaluationContext
properties. We set the Principal property to our own TestPrincipal
object, and to set the Identities property to use our own TestIdentity
. These test objects are rigged to always return our required test principal and identity:
internal class TestPrincipal : IPrincipal { public bool IsInRole(string role) { return true; } public IIdentity Identity { get { return new TestIdentity(); } } } internal class TestIdentity : IIdentity { public string Name { get { return @"MyTestDomain\MyTestUser"; } } public string AuthenticationType { get { return "Dodgy auth"; } } public bool IsAuthenticated { get { return true; } } }
The integration test can then be run like this:
[Test] [Category("Integration")] public void Should_be_able_to_call_my_poxy_WCF_service() { using (var channelFactory = new ChannelFactory<IMyService>(serviceEndpoint)) { var service = channelFactory.CreateChannel(); var output = service.DoStuff("Please work!"); Assert.That(output, Is.EqualTo("Worked! Hurrah!")); ((IChannel)service).Close(); } }
So here’s what we’ve done. We’ve self-hosted a WCF service in the test fixture, and overridden the authorisation process used by that host to make it look like we always have an authenticated "MyTestDomain\MyTestUser" user. We then created a ChannelFactory
to connect to that host and test the service from end-to-end (well, except for the authorisation part things ;-)). You can obviously muck around with the custom authorisation bits to change the user on the fly, so you can test different users and situations where the user is not authenticated.
Hope this helps. Let me know if I’ve missed easier ways of doing this :-)