12 Jan 2012, 21:43
Generic-user-small

Anthony (1 post)

Hello everyone,

I have a small extension class that wraps a call to a WCF service and properly closes or aborts the connection. It contains two methods : one for synchronous calls (it cleans things up automatically once the call finishes) and another for asynchronous calls (in which you manually tell it to clean up since the async code will still be executing long after the call finishes). Here is the code :

using System;

namespace ServiceFramework
	{
	public static class WcfExtensions
		{
		public static void Use<TChannel>(this IChannelFactory<TChannel> channelFactory, Action<TChannel> innerAction) where TChannel : class
			{
			if (innerAction == null)
				{
				throw new ArgumentNullException("innerAction");
				}

			UseAsync
				(
				channelFactory,
				outerAction =>
					{
					Exception exception = null;

					try
						{
						innerAction(outerAction.Channel);
						}
					catch (Exception ex)
						{
						exception = ex;
						}
					finally
						{
						outerAction.Close(exception);
						}
					}
				);
			}

		public static void UseAsync<TChannel>(this IChannelFactory<TChannel> channelFactory, Action<ChannelProvider<TChannel>> action) where TChannel : class
			{
			if (action == null)
				{
				throw new ArgumentNullException("action");
				}

			var channel = channelFactory.CreateChannel();
			var channelProvider = new ChannelProvider<TChannel>(channel);
			action(channelProvider);
			}
		}
	}

The way one would use this is as follows :

ChannelFactory<ICalculatorService> calculatorServiceFactory = ...;

// sync method
calculatorServiceFactory.Use
   (
   service =>
      {
      var result = service.Multiply(9, 9);
      // do something with result
      }
   );

As you can see, I don’t need to create a new service client nor do I have to worry about closing it; it’s all taken care of behind the scenes.

// async method
calculatorServiceFactory.UseAsync
   (
   channelProvider =>
      {
      channelProvider.Channel.BeginMultiply
         (
         9, 
         9,
         asyncResult =>
            {
            var result = channelProvider.Channel.EndMultiply(asyncResult)
            channelProvider.Close(); // manual clean up

            // do something with result
            },
         null
         );
      }
   );

As you can see, all I had to do was call the Close method on the supplied ChannelProvider for it to perform cleanup. A manual call to Close was necessary due to the asynchronous nature of the call.

I came up with 36 unit tests totalling 900 lines of code that should cover both tests; my question is, is this overkill given that the class under test is only 50 lines? I honestly don’t look at number of lines when creating unit tests; I just test all possibilities… but I don’t know how to justify (to my superiors @ work) all the time spent creating these tests. I’m also adhering to the “only one assert per test” philosophy, which is a contributing factor. Any thoughts? Is this simply the price we pay for ensuring code correctness, or am I doing something very, very wrong?

Here are my unit test method signatures :

		public void Use_OnNullActionParameter_ThrowsArgumentNullException()
		public void Use_ChannelInputParameterProvidedByActionLambda_IsSameAsChannelCreatedByFactory()
		public void Use_OnExceptionDuringChannelCreation_DoesNotAutomaticallyCallAbort()
		public void Use_OnExceptionDuringChannelCreation_DoesNotAutomaticallyCallClose()
		public void Use_OnExceptionDuringChannelCreation_DoesNotExecuteAction()
		public void Use_OnTimeoutExceptionDuringAction_AutomaticallyAborts()
		public void Use_OnTimeoutExceptionDuringAction_DoesNotAutomaticallyClose()
		public void Use_OnCommunicationExceptionDuringAction_AutomaticallyAborts()
		public void Use_OnCommunicationExceptionDuringAction_DoesNotAutomaticallyClose()
		public void Use_OnNonWcfExceptionDuringAction_AutomaticallyCloses()
		public void Use_OnNonWcfExceptionDuringAction_DoesNotAutomaticallyAbort()
		public void Use_OnNoExceptionDuringAction_AutomaticallyCloses()
		public void Use_OnNoExceptionDuringAction_DoesNotAutomaticallyAbort()
		public void UseAsync_OnNullActionParameter_ThrowsArgumentNullException()
		public void UseAsync_ChannelInputParameterProvidedByActionLambda_IsSameAsChannelCreatedByFactory()
		public void UseAsync_OnExceptionDuringChannelCreation_DoesNotAutomaticallyCallAbort()
		public void UseAsync_OnExceptionDuringChannelCreation_DoesNotAutomaticallyCallClose()
		public void UseAsync_OnExceptionDuringChannelCreation_DoesNotExecuteAction()
		public void UseAsync_OnTimeoutExceptionDuringAction_DoesNotAutomaticallyCallAbort()
		public void UseAsync_OnTimeoutExceptionDuringAction_DoesNotAutomaticallyCallClose()
		public void UseAsync_OnCommunicationExceptionDuringAction_DoesNotAutomaticallyCallAbort()
		public void UseAsync_OnCommunicationExceptionDuringAction_DoesNotAutomaticallyCallClose()
		public void UseAsync_OnNonWcfExceptionDuringAction_DoesNotAutomaticallyCallAbort()
		public void UseAsync_OnNonWcfExceptionDuringAction_DoesNotAutomaticallyCallClose()
		public void UseAsync_OnNoExceptionDuringAction_DoesNotAutomaticallyCallAbort()
		public void UseAsync_OnNoExceptionDuringAction_DoesNotAutomaticallyCallClose()
		public void UseAsync_OnManualCloseWithTimeoutExceptionArgumentDuringAction_AutomaticallyCallsAbort()
		public void UseAsync_OnManualCloseWithTimeoutExceptionArgumentDuringAction_DoesNotAutomaticallyCallClose()
		public void UseAsync_OnManualCloseWithCommunicationExceptionArgumentDuringAction_AutomaticallyCallsAbort()
		public void UseAsync_OnManualCloseWithCommunicationExceptionArgumentDuringAction_DoesNotAutomaticallyCallClose()
		public void UseAsync_OnManualCloseWithNonWcfExceptionArgumentDuringAction_AutomaticallyCallsClose()
		public void UseAsync_OnManualCloseWithNonWcfExceptionArgumentDuringAction_DoesNotAutomaticallyCallAbort()
		public void UseAsync_OnManualCloseWithNullArgumentDuringAction_AutomaticallyCallsClose()
		public void UseAsync_OnManualCloseWithNullArgumentDuringAction_DoesNotAutomaticallyCallAbort()
		public void UseAsync_OnManualCloseWithNoArgumentDuringAction_AutomaticallyCallsClose()
		public void UseAsync_OnManualCloseWithNoArgumentDuringAction_DoesNotAutomaticallyCallAbort()

Here’s a sample of one of my tests :

		[Fact]
		public void Use_OnTimeoutExceptionDuringAction_AutomaticallyAborts()
			{
			var stubChannelFactory = MockRepository.GenerateStub<IChannelFactory<IQueryService>>();
			var mockQueryService = MockRepository.GenerateMock<IQueryService, IClientChannel>(); // note : unfortunately GenerateStub doesn't support multiple interfaces

			stubChannelFactory.Stub(x => x.CreateChannel()).Return(mockQueryService);
			(mockQueryService as IClientChannel).Expect(x => x.State).Return(CommunicationState.Faulted);

			try
				{
				stubChannelFactory.Use
					(
					p =>
						{
						throw new TimeoutException();
						}
					);
				}
			catch (Exception)
				{
				}

			(mockQueryService as IClientChannel).AssertWasCalled(x => x.Abort());
			}

Anthony

  You must be logged in to comment