TDD and WCF behavior
- by Frederic Hautecoeur
Some weeks ago I wanted to develop a WCF behavior using TDD. I have lost some time trying to use mocks. After a while i decided to just use a host and a client. I don’t like this approach but so far I haven’t found a good and fast solution to use Unit Test for testing a WCF behavior. To Implement my solution I had to : Create a Dummy Service Definition; Create the Dummy Service Implementation; Create a host; Create a client in my test; Create and Add the behavior; Dummy Service Definition This is just a simple service, composed of an Interface and a simple implementation. The structure is aimed to be easily customizable for my future needs. Using Clauses : 1: using System.Runtime.Serialization;
2: using System.ServiceModel;
3: using System.ServiceModel.Channels;
The DataContract:
1: [DataContract()]
2: public class MyMessage
3: {
4: [DataMember()]
5: public string MessageString;
6: }
The request MessageContract:
1: [MessageContract()]
2: public class RequestMessage
3: {
4: [MessageHeader(Name = "MyHeader", Namespace = "http://dummyservice/header", Relay = true)]
5: public string myHeader;
6:
7: [MessageBodyMember()]
8: public MyMessage myRequest;
9: }
The response MessageContract:
1: [MessageContract()]
2: public class ResponseMessage
3: {
4: [MessageHeader(Name = "MyHeader", Namespace = "http://dummyservice/header", Relay = true)]
5: public string myHeader;
6:
7: [MessageBodyMember()]
8: public MyMessage myResponse;
9: }
The ServiceContract:
1: [ServiceContract(Name="DummyService", Namespace="http://dummyservice",SessionMode=SessionMode.Allowed )]
2: interface IDummyService
3: {
4: [OperationContract(Action="Perform", IsOneWay=false, ProtectionLevel=System.Net.Security.ProtectionLevel.None )]
5: ResponseMessage DoThis(RequestMessage request);
6: }
Dummy Service Implementation
1: public class DummyService:IDummyService
2: {
3: #region IDummyService Members
4: public ResponseMessage DoThis(RequestMessage request)
5: {
6: ResponseMessage response = new ResponseMessage();
7: response.myHeader = "Response";
8: response.myResponse = new MyMessage();
9: response.myResponse.MessageString =
10: string.Format("Header:<{0}> and Request was <{1}>",
11: request.myHeader, request.myRequest.MessageString);
12: return response;
13: }
14: #endregion
15: }
Host Creation
The most simple host implementation using a Named Pipe binding. The GetBinding method will create a binding for the host and can be used to create the same binding for the client.
1: public static class TestHost
2: {
3:
4: internal static string hostUri = "net.pipe://localhost/dummy";
5:
6: // Create Host method.
7: internal static ServiceHost CreateHost()
8: {
9: ServiceHost host = new ServiceHost(typeof(DummyService));
10:
11: // Creating Endpoint
12: Uri namedPipeAddress = new Uri(hostUri);
13: host.AddServiceEndpoint(typeof(IDummyService), GetBinding(), namedPipeAddress);
14:
15: return host;
16: }
17:
18: // Binding Creation method.
19: internal static Binding GetBinding()
20: {
21: NamedPipeTransportBindingElement namedPipeTransport = new NamedPipeTransportBindingElement();
22: TextMessageEncodingBindingElement textEncoding = new TextMessageEncodingBindingElement();
23:
24: return new CustomBinding(textEncoding, namedPipeTransport);
25: }
26:
27: // Close Method.
28: internal static void Close(ServiceHost host)
29: {
30: if (null != host)
31: {
32: host.Close();
33: host = null;
34: }
35: }
36: }
Checking the service
A simple test tool check the plumbing.
1: [TestMethod]
2: public void TestService()
3: {
4: using (ServiceHost host = TestHost.CreateHost())
5: {
6: host.Open();
7:
8: using (ChannelFactory<IDummyService> channel =
9: new ChannelFactory<IDummyService>(TestHost.GetBinding()
10: , new EndpointAddress(TestHost.hostUri)))
11: {
12: IDummyService svc = channel.CreateChannel();
13: try
14: {
15: RequestMessage request = new RequestMessage();
16: request.myHeader = Guid.NewGuid().ToString();
17: request.myRequest = new MyMessage();
18: request.myRequest.MessageString = "I want some beer.";
19:
20: ResponseMessage response = svc.DoThis(request);
21: }
22: catch (Exception ex)
23: {
24: Assert.Fail(ex.Message);
25: }
26: }
27: host.Close();
28: }
29: }
Running the service should show that the client and the host are running fine.
So far so good.
Adding the Behavior
Add a reference to the Behavior project and add the using entry in the test class.
We just need to add the behavior to the service host :
1: [TestMethod]
2: public void TestService()
3: {
4: using (ServiceHost host = TestHost.CreateHost())
5: {
6: host.Description.Behaviors.Add(new MyBehavior());
7: host.Open();¨
8: …
If you set a breakpoint in your behavior and run the test in debug mode, you will hit the breakpoint.
In this case I used a ServiceBehavior.
To add an Endpoint behavior you have to add it to the endpoints.
1: host.Description.Endpoints[0].Behaviors.Add(new MyEndpointBehavior())
To add a contract or an operation behavior a custom attribute should work on the service contract definition. I haven’t tried that yet.
All the code provided in this blog and in the following files are for sample use.
Improvements
I don’t like to instantiate a client and a service to test my behaviors. But so far I have' not found an easy way to do it.
Today I am passing a type of endpoint to the host creator and it creates the right binding type. This allows me to easily switch between bindings at will.
I have used the same approach to test Mex Endpoints, another post should come later for this.
Enjoy !