We have a number of services in our project. Our clients include WPF, iOS, Android and Web. To have the best interoperability we communicate over http and expose WSDL for proxy generation. This means we cannot change the contracts or introduce new versions when we break compatibility. We plan to test the compatibility of our changes with unit tests.
The unit tests host services based on the latest contract. Then every released proxy is used to call the known operations. When the call succeeds, the results and parameters are validated on both ends. This way we know what is broken.
To test the async calls to the services I’m using a Task. See the code below, other methods are left out for clarity.
[TestMethod] public void Print_async() { // Arrange var mock = GenerateServiceMock(); mock.Expect(x => x.Print(null)).IgnoreArguments().Return(1) .WhenCalled((x) => Thread.Sleep(3000)); // included to simulate lag // Act var actual = default(int); using (var host = StartService(mock)) { var proxy = GenerateProxy(); // call print direct // var actual = proxy.Print("Hello World"); // call print async var async = proxy.BeginPrint("Hello World", null, null); // task to handle async var task = Task.Factory.FromAsync(async, (x) => actual = proxy.EndPrint(x)); // wait for it task.Wait(); // cleanup host.Close(); } // Assert Assert.AreEqual<int>(1, actual); var args = mock.GetArgumentsForCallsMadeOn(x => x.Print(null)); mock.VerifyAllExpectations(); Assert.AreEqual("Hello World", args[0][0]); }
Because our project is on the 4.0 version of the dotnet framework we can’t use async/await. The FromAsync method takes the IAsyncResult from the BeginPrint call and waits for it to be completed. When it completes the EndPrint method is called to receive the result from the server. Until the task we’ve created finishes the execution is put on hold with task.Wait().
The code for testing the service direct or async only differs in the call of the operation. The Arrange and Assert sections are untouched.
Complete sample below
// you'll need rhino mocks: http://hibernatingrhinos.com/downloads/rhino-mocks/latest [ServiceContract] public interface IPrintable { [OperationContract] int Print(string document); [OperationContract (AsyncPattern = true)] IAsyncResult BeginPrint(string document, AsyncCallback callback, object state); int EndPrint(IAsyncResult async); } private IPrintable GenerateServiceMock() { Castle.DynamicProxy.Generators.AttributesToAvoidReplicating.Add<ServiceContractAttribute>(); var mock = MockRepository.GenerateStrictMock<IPrintable>(); return mock; } private ServiceHost StartService(IPrintable mock) { var service = new ServiceHost(mock, new Uri("http://localhost:1233/printservice")); // ! enable hosting an instance (not a type) service.Description.Behaviors.Find<ServiceBehaviorAttribute>().InstanceContextMode = InstanceContextMode.Single; service.Open(); return service; } private IPrintable GenerateProxy() { // abc for proxy var address = new EndpointAddress("http://localhost:1233/printservice"); var binding = new BasicHttpBinding(); var channel = ChannelFactory<IPrintable>.CreateChannel(binding, address); return channel; } [TestMethod] public void Print() { // Arrange var mock = GenerateServiceMock(); mock.Expect(x => x.Print(null)).IgnoreArguments().Return(1); // Act var actual = default(int); using (var host = StartService(mock)) { // abc for proxy var proxy = GenerateProxy(); // call print async actual = proxy.Print("Hello World"); // cleanup host.Close(); } // Assert Assert.AreEqual<int>(1, actual); var args = mock.GetArgumentsForCallsMadeOn(x => x.Print(null)); mock.VerifyAllExpectations(); Assert.AreEqual("Hello World", args[0][0]); } [TestMethod] public void Print_async() { // Arrange var mock = GenerateServiceMock(); mock.Expect(x => x.Print(null)).IgnoreArguments().Return(1) .WhenCalled((x) => System.Threading.Thread.Sleep(3000)); // Act var actual = default(int); using (var host = StartService(mock)) { // abc for proxy var proxy = GenerateProxy(); // call print async var async = proxy.BeginPrint("Hello World", null, null); // wait for it var task = Task.Factory.FromAsync(async, (x) => actual = proxy.EndPrint(x)); task.Wait(); // cleanup host.Close(); } // Assert Assert.AreEqual<int>(1, actual); var args = mock.GetArgumentsForCallsMadeOn(x => x.Print(null)); mock.VerifyAllExpectations(); Assert.AreEqual("Hello World", args[0][0]); }