Part 2
Part 3
Part 4 you are here
In the previous post, we had setup our basic WCF project to play around with for unit testing. Since then, I have pulled out the WCF service reference in the console application, and placed it in our business project. Now that the business project has the WCF service reference, I have added a new class that handles the newing up of the WCF client.
Additionally, I have updated the console application to use the new business object wrapper of the WCF client.
Both of those classes look like this:
Program.cs
namespace RussUnitTestSample { class Program { #region consts const string CONNECTION_STRING = "Data Source=192.168.50.4,1515;Initial Catalog=MBES;Persist Security Info=True;Integrated Security=true;"; #endregion consts #region Entry static void Main(string[] args) { GetNumbersAndAddThem obj = new GetNumbersAndAddThem( new DbGetSomeNumbers(new BaseDbConnection(CONNECTION_STRING)), new NumberFunctions() ); Console.WriteLine("\n"); Console.WriteLine(obj.Execute()); Console.WriteLine("\n"); Business.WCF.Service1 service = new Business.WCF.Service1(); Console.WriteLine("\n"); Console.WriteLine("{0}", service.GetData(42)); Console.WriteLine("\n"); } #endregion Entry } }
WCF.Service1
namespace RussUnitTestSample.Business.WCF { /// <summary> /// Communication with the WCF Service1 /// </summary> public class Service1 { #region Private private ServiceReference1.Service1Client _service; #endregion Private public Service1() { this._service = new ServiceReference1.Service1Client(); } public string GetData(int value) { return this._service.GetData(value); } } }
I moved the WCF service and newing up of that client from the console application to make it easier to unit test. We are still not at a point that WCF.Service1 can be unit tested, though the service itself can.
I’ve added a new RussUnitTestSample.WCF.Tests project to my solution, and added the following tests for my Service1.svc class (the implementation of IService1).
As a reminder the IService1.cs was defined as:
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together. [ServiceContract] public interface IService1 { [OperationContract] string GetData(int value); [OperationContract] CompositeType GetDataUsingDataContract(CompositeType composite); // TODO: Add your service operations here }
I have added the following unit tests based on the implementation in
Service1.svc:
namespace RussUnitTestSample.Wcf.Tests { /// <summary> /// Unit tests for service1 /// </summary> [TestClass] [ExcludeFromCodeCoverage] public class Service1Tests { /// <summary> /// Get data works as expected with a positive number /// </summary> [TestMethod] public void Service1_GetData_PositiveNumber() { // Arrange Wcf.Service1 service = new Wcf.Service1(); int num = 55; var expected = string.Format("You entered: {0}", num); // Act var result = service.GetData(num); // Assert Assert.AreEqual(expected, result); } /// <summary> /// Get data works as expected with a negative number /// </summary> [TestMethod] public void Service1_GetData_NegativeNumber() { // Arrange Wcf.Service1 service = new Wcf.Service1(); int num = -42; var expected = string.Format("You entered: {0}", num); // Act var result = service.GetData(num); // Assert Assert.AreEqual(expected, result); } /// <summary> /// Get data works as expected with zero /// </summary> [TestMethod] public void Service1_GetData_Zero() { // Arrange Wcf.Service1 service = new Wcf.Service1(); int num = 0; var expected = string.Format("You entered: {0}", num); // Act var result = service.GetData(num); // Assert Assert.AreEqual(expected, result); } /// <summary> /// An exception is thrown when the CompositeType is null /// </summary> [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void Service1_GetDataUsingDataContract_ExceptionThrownWhenCompositeTypeNull() { // Arrange Wcf.Service1 service = new Service1(); // Act var result = service.GetDataUsingDataContract(null); } /// <summary> /// When BoolValue is false, do not append "Suffix" to StringValue /// </summary> [TestMethod] public void Service1_GetDataUsingDataContract_CompositTypeBoolValueFalse_DoNotAppendSuffix() { // Arrange Wcf.Service1 service = new Service1(); string testString = "Test"; CompositeType ct = new CompositeType() { BoolValue = false, StringValue = testString }; // Act var result = service.GetDataUsingDataContract(ct); // Assert Assert.AreEqual(testString, result.StringValue); } /// <summary> /// When BoolValue is true, append "Suffix" to StringValue /// </summary> [TestMethod] public void Service1_GetDataUsingDataContract_CompositTypeBoolValueTrue_AppendSuffix() { // Arrange Wcf.Service1 service = new Service1(); string testString = "Test"; CompositeType ct = new CompositeType() { BoolValue = true, StringValue = testString }; var expected = testString + "Suffix"; // Act var result = service.GetDataUsingDataContract(ct); // Assert Assert.AreEqual(expected, result.StringValue); } } }
Code Coverage:
Taking a look at our code coverage, you can see that currently we have 100% coverage for our RussUnitTestSample.Wcf project, but our coverage of RussUnitTestSample.Business has gone from 100, to 54.21. This is expected of course, as we have added a Wcf Service reference, as well as a wrapper of the WCF client. I think we could technically unit test the Service Reference code, but it is auto generated, so I think I’m going to ignore it for now. Wonder if I can exclude it from Code Coverage.
So now let’s look into how to go about testing our Business.Wcf client wrapper.
WCF.Service1
namespace RussUnitTestSample.Business.WCF { /// <summary> /// Communication with the WCF Service1 /// </summary> public class Service1 { #region Private private ServiceReference1.Service1Client _service; #endregion Private public Service1() { this._service = new ServiceReference1.Service1Client(); } public string GetData(int value) { return this._service.GetData(value); } } }
As this class currently stands, we’re working with a Service1Client and not an interface, so it’s difficult to unit test. Let’s do a little refactoring. Instead of newing up the Service1Client, let’s take in the interface of said client. After updating our class looks like:
namespace RussUnitTestSample.Business.WCF { /// <summary> /// Communication with the WCF Service1 /// </summary> public class Service1 { #region Private private IService1 _service; #endregion Private #region ctor /// <summary> /// Constructor - new up IService1 with client /// </summary> public Service1() { this._service = new Service1Client(); } /// <summary> /// Constructor - takes in implementation of IService1 /// </summary> /// <param name="service">The IService1 implementation public Service1(IService1 service) { if (service == null) throw new ArgumentNullException(nameof(service)); this._service = service; } #endregion ctor #region Public methods /// <summary> /// Call service GetData /// </summary> /// <param name="value">The value to pass to the WCF service /// <returns>The returned value from the WCF service call</returns> public string GetData(int value) { return this._service.GetData(value); } #endregion Public methods } }
Now that we’re taking in an interface of the service, we can write some unit tests:
RussUnitTestSample.Business.Tests.Wcf.Service1Tests.cs
namespace RussUnitTestSample.Business.Tests.WCF { /// <summary> /// Unit tests for Service1 /// </summary> [TestClass] [ExcludeFromCodeCoverage] public class Service1Tests { #region Private private Mock<iservice1> _service; #endregion Private #region Public methods /// <summary> /// initialize the mocks /// </summary> [TestInitialize] public void Setup() { this._service = new Mock<iservice1>(); } /// <summary> /// Exception thrown when IService implementation is not provided /// </summary> [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void Service1_NullIService1InConstructor_ThrowsException() { // Arrange / Act Business.WCF.Service1 service = new Business.WCF.Service1(null); } /// <summary> /// Object properly constructed when implementation of IService1 provided /// </summary> [TestMethod] public void Service1_ConstructorWithProvidedIService1_NewsCorrectly() { // Arrange / Act Business.WCF.Service1 service = new Business.WCF.Service1(_service.Object); // Assert Assert.IsInstanceOfType(service, typeof(Business.WCF.Service1)); } /// <summary> /// Ensure that a string is returned from Service1 when calling GetData /// </summary> [TestMethod] public void Service1_GetDataTest() { // Arrange this._service.Setup(s => s.GetData(It.IsAny<int>())).Returns("test"); Business.WCF.Service1 service = new Business.WCF.Service1(_service.Object); // Act var result = service.GetData(It.IsAny<int>()); // Assert Assert.IsInstanceOfType(result, typeof(string)); } #endregion Public methods } }
And our new code coverage:
Now we’ve hit everything except the default constructor used for Service1. Guess I’ll have to figure out how to accomplish that later. Also I added a .runsettings file to exclude “Service Reference” folders from code coverage.
Latest code as of post:
https://github.com/Kritner/RussUnitTestSample/tree/b9c2f329adbc700688fb69943cc4b7b28ffd87c4
No comments:
Post a Comment