Introducing the Expected Objects Library

On June 28, 2011, in Uncategorized, by derekgreer

Introduced in the Effective Test Series, the Expected Object pattern is a technique involving the encapsulation of test-specific logic within a specialized type designed to compare its configured state against that of another object. Use of the Expected Object pattern eliminates the need to encumber system objects with test-specific equality behavior, helps to reduce test code duplication and can aid in expressing the logical intent of automated tests.

While the Expected Object pattern is a great strategy for helping adhere to good testing practices, the process of actually implementing the required types can be less than motivating. To alleviate the burden of hand-rolling Expected Object types, I created the Expected Objects library. This library provides the ability to compare the state of one object against another without relying upon the provided type’s equality members. In addition to the ability to assert equality, the library also provides equality assertion methods which provide feedback of how each member of an object differs from an expected state.

The following examples demonstrate the capabilities of the library:

 

Comparing Flat Objects

public class when_retrieving_a_customer
{
	static Customer _actual;
	static ExpectedObject _expected;

	Establish context = () =>
		{
			_expected = new Customer
				            {
				            	Name = "Jane Doe",
				            	PhoneNumber = "5128651000"
				            }.ToExpectedObject();

			_actual = new Customer
				          {
				          	Name = "John Doe",
				          	PhoneNumber = "5128654242"
				          };
		};

	It should_return_the_expected_customer = () => _expected.ShouldEqual(_actual);
}



class Customer
{
	public string Name { get; set; }
	public string PhoneNumber { get; set; }
}

Results:

should return the expected customer : Failed For Customer.Name, expected "Jane Doe" but found "John Doe". For Customer.PhoneNumber, expected "5128651000" but found "5128654242".

 

Comparing Composed Objects

public class when_retrieving_a_customer_with_address
{
	static Customer _actual;
	static ExpectedObject _expected;

	Establish context = () =>
		{
			_expected = new Customer
				            {
				            	Name = "Jane Doe",
				            	PhoneNumber = "5128651000",
				            	Address = new Address
				            		          {
				            		          	AddressLineOne = "123 Street",
				            		          	AddressLineTwo = string.Empty,
				            		          	City = "Austin",
				            		          	State = "TX",
				            		          	Zipcode = "78717"
				            		          }
				            }.ToExpectedObject();

			_actual = new Customer
				          {
				          	Name = "John Doe",
				          	PhoneNumber = "5128654242",
				          	Address = new Address
				          		          {
				          		          	AddressLineOne = "456 Street",
				          		          	AddressLineTwo = "Apt. 3",
				          		          	City = "Waco",
				          		          	State = "TX",
				          		          	Zipcode = "76701"
				          		          }
				          };
		};

	It should_return_the_expected_customer = () => _expected.ShouldEqual(_actual);
}



class Customer
{
	public string Name { get; set; }
	public string PhoneNumber { get; set; }
	public Address Address { get; set; }
}



class Address
{
	public string AddressLineOne { get; set; }
	public string AddressLineTwo { get; set; }
	public string City { get; set; }
	public string State { get; set; }
	public string Zipcode { get; set; }
}

Results:

should return the expected customer : Failed For Customer.Name, expected "Jane Doe" but found "John Doe". For Customer.PhoneNumber, expected "5128651000" but found "5128654242". For Customer.Address.AddressLineOne, expected "123 Street" but found "456 Street". For Customer.Address.AddressLineTwo, expected "" but found "Apt. 3". For Customer.Address.City, expected "Austin" but found "Waco". For Customer.Address.Zipcode, expected "78717" but found "76701".

 

Comparing Collections

public class when_retrieving_a_collection_of_customers
{
	static List<Customer> _actual;
	static ExpectedObject _expected;

	Establish context = () =>
		{
			_expected = new List<Customer>
				            {
				            	new Customer {Name = "Customer A"},
				            	new Customer {Name = "Customer B"}
				            }.ToExpectedObject();

			_actual = new List<Customer>
				          {
				          	new Customer {Name = "Customer A"},
				          	new Customer {Name = "Customer C"}
				          };
		};

	It should_return_the_expected_customers = () => _expected.ShouldEqual(_actual);
}

Results:

should return the expected customers : Failed For List`1[1].Name, expected "Customer B" but found "Customer C".

 

Comparing Dictionaries

public class when_retrieving_a_dictionary
{
	static IDictionary<string, string> _actual;
	static IDictionary<string, string> _expected;

	static bool _result;

	Establish context = () =>
		{
			_expected = new Dictionary<string, string> {{"key1", "value1"}};
			_actual = new Dictionary<string, string> {{"key1", "value1"}, {"key2", "value2"}};
		};

	It should_return_the_expected_dictionary = () => _expected.ToExpectedObject().ShouldEqual(_actual);
}

Results:

should return the expected dictionary : Failed For Dictionary`2[1], expected nothing but found [[key2, value2]].

 

Comparing Types with Indexes

public class when_retrieving_a_type_with_an_index
{
	static IndexType _actual;
	static IndexType _expected;

	static bool _result;

	Establish context = () =>
		{
			_expected = new IndexType(new List<int> {1, 2, 3, 4, 6});
			_actual = new IndexType(new List<int> {1, 2, 3, 4, 5});
		};

	It should_return_the_expected_type = () => _expected.ToExpectedObject().ShouldEqual(_actual);
}



class IndexType
{
	readonly IList<T> _ints;

	public IndexType(IList<T> ints)
	{
		_ints = ints;
	}

	public T this[int index]
	{
		get { return _ints[index]; }
	}

	public int Count
	{
		get { return _ints.Count; }
	}
}

Results:

should return the expected type : Failed For IndexType`1.Item[4], expected [6] but found [5].

 

Comparing Partial Objects

public class when_retrieving_a_customer
{
	static Customer _actual;
	static ExpectedObject _expected;

	Establish context = () =>
		{
			_expected = new
				            {
				            	Name = "Jane Doe",
				            	Address = new
				            		          {
				            		          	City = "Austin"
				            		          }
				            }.ToExpectedObject();

			_actual = new Customer
				          {
				          	Name = "John Doe",
				          	PhoneNumber = "5128654242",
				          	Address = new Address
				          		          {
				          		          	AddressLineOne = "456 Street",
				          		          	AddressLineTwo = "Apt. 3",
				          		          	City = "Waco",
				          		          	State = "TX",
				          		          	Zipcode = "76701"
				          		          }
				          };
		};

	It should_have_the_correct_name_and_address = () => _expected.ShouldMatch(_actual);
}

Results:

should have the correct name and address : Failed For Customer.Name, expected "Jane Doe" but found "John Doe". For Customer.Address.City, expected "Austin" but found "Waco".

 

Extensibility

The Expected Objects library is extensible, so if it doesn’t provide the exact comparison strategies you need then you’re free to add our own.

The main extensibility point is the IComparisonStrategy which is declared as follows:

public interface IComparisonStrategy
{
    bool CanCompare(Type type);
    bool AreEqual(object expected, object actual, IComparisonContext comparisonContext);
}

To register a custom strategy, simply call the Configure() method and use the supplied ConfigurationContext to call the PushStrategy<t>() method:

_expected = new Foo("Bar")
	.ToExpectedObject()
	.Configure(ctx => ctx.PushStrategy<FooComparisonStrategy>());

This will push the custom strategy onto the stack used by the Expected Objects library during its comparisons.

Custom Comparison Strategy Example

The following demonstrates how the Expected Objects library could be extended to compare an expected object to the contents of a Web page.

Consider the following specification:

public class when_displaying_the_customer_view
{
	static Mock<IWebDriver> _actual;
	static ExpectedObject _expected;

	Establish context = () =>
		{
			var nameElementStub = new Mock<IWebElement>();
			nameElementStub.Setup(x => x.Text).Returns("Jane Doe");
			var addressElementStub = new Mock<IWebElement>();
			addressElementStub.Setup(x => x.Text).Returns("456 Street");
			var buttonElementStub = new Mock<IWebElement>();
			buttonElementStub.Setup(x => x.Text).Returns("Cancel");
			_actual = new Mock<IWebDriver>();
			_actual.Setup(x => x.FindElement(By.Id("name"))).Returns(nameElementStub.Object);
			_actual.Setup(x => x.FindElement(By.CssSelector("input[name='address']"))).Returns(addressElementStub.Object);
			_actual.Setup(x => x.FindElement(By.XPath("//input[@value='submit']"))).Returns(buttonElementStub.Object);

			_expected = new ExpectedView()
				.WithId("name", "John Doe")
				.WithCssSelector("input[name='address']", "123 Street")
				.WithXPath("//input[@value='submit']", "Submit")
				.ToExpectedObject()
				.Configure(ctx =>
					{
						ctx.PushStrategy<ExpecedViewComparisonStrategy>();
						ctx.IgnoreTypes();
					});
		};

	It should_display_the_expected_view = () => _expected.ShouldEqual(_actual.Object);
}

Here, the Selenium 2 IWebDriver type is being stubbed to emulate an active Selenium testing session. Next, a custom ExpectedView type is instantiated and configured to expect one value to be located by an Id, one by a CSS Selector and one by an XPath. Lastly, the expected object is compared to the actual object (in this case, the IWebDriver stub).

Executing the specification produces the following results:

should display the expected view : Failed For IWebDriverProxy.FindElement(By.Id("name")), expected "John Doe" but found "Jane Doe". For IWebDriverProxy.FindElement(By.CssSelector("input[name='address']")), expected "123 Street" but found "456 Street". For IWebDriverProxy.FindElement(By.XPath("//input[@value='submit']")), expected "Submit" but found "Cancel".

 

Here is the ExpectedView and ExpectedViewComparisonStrategy implementation:

class ExpectedView
{
	public ExpectedView()
	{
		Ids = new List<Tuple<string, string>>();
		CssSelectors = new List<Tuple<string, string>>();
		XPaths = new List<Tuple<string, string>>();
	}

	public List<Tuple<string, string>> Ids { get; private set; }
	public List<Tuple<string, string>> CssSelectors { get; private set; }
	public List<Tuple<string, string>> XPaths { get; private set; }

	public ExpectedView WithId(string name, string value)
	{
		Ids.Add(new Tuple<string, string>(name, value));
		return this;
	}

	public ExpectedView WithCssSelector(string selector, string value)
	{
		CssSelectors.Add(new Tuple<string, string>(selector, value));
		return this;
	}

	public ExpectedView WithXPath(string path, string value)
	{
		XPaths.Add(new Tuple<string, string>(path, value));
		return this;
	}
}



class ExpecedViewComparisonStrategy : IComparisonStrategy
{
	public bool CanCompare(Type type)
	{
		return typeof (ExpectedView).IsAssignableFrom(type);
	}

	public bool AreEqual(object expected, object actual, IComparisonContext comparisonContext)
	{
		bool areEqual = true;
		var view = (ExpectedView) expected;
		var driver = (IWebDriver) actual;
		view.Ids.ForEach(id => areEqual = CompareIds(driver, id, comparisonContext) && areEqual);
		view.CssSelectors.ForEach(selector => areEqual = CompareCssSelectors(driver, selector, comparisonContext) && areEqual);
		view.XPaths.ForEach(path => areEqual = CompareXPaths(driver, path, comparisonContext) && areEqual);
		return areEqual;
	}

	static bool CompareIds(IWebDriver driver, Tuple<string, string> expected, IComparisonContext comparisonContext)
	{
		bool areEqual = true;
		IWebElement idElement = driver.FindElement(By.Id(expected.Item1));
		areEqual = comparisonContext.AreEqual(expected.Item2, idElement.Text, "FindElement(By.Id(\"" + expected.Item1 + "\"))") && areEqual;
		return areEqual;
	}

	static bool CompareCssSelectors(IWebDriver driver, Tuple<string, string> expected, IComparisonContext comparisonContext)
	{
		bool areEqual = true;
		IWebElement idElement = driver.FindElement(By.CssSelector(expected.Item1));
		areEqual = comparisonContext.AreEqual(expected.Item2, idElement.Text, "FindElement(By.CssSelector(\"" + expected.Item1 + "\"))") && areEqual;
		return areEqual;
	}

	static bool CompareXPaths(IWebDriver driver, Tuple<string, string> expected, IComparisonContext comparisonContext)
	{
		bool areEqual = true;
		IWebElement idElement = driver.FindElement(By.XPath(expected.Item1));
		areEqual = comparisonContext.AreEqual(expected.Item2, idElement.Text, "FindElement(By.XPath(\"" + expected.Item1 + "\"))") && areEqual;
		return areEqual;
	}
}

Note: This example is for demonstration purposes only.

 

Conclusion

The Expected Objects library is published as a NuGet package and the source is hosted on github. Feel free to provide feedback.

Tagged with:  

Effective Tests: Expected Objects

On June 24, 2011, in Uncategorized, by derekgreer

In the last installment of the Effective Tests series, the topic of Custom Assertions was presented as a strategy for helping to clarify the intent of our tests. This time we’ll take a look at another test pattern for improving the communication of our tests in addition to reducing test code duplication and the need to add test-specific code to our production types.

Expected Objects

Writing tests often involves inspecting the state of collaborating objects and the messages they exchange within a system. This often leads to declaring multiple assertions on fields of the same object which can lead to several maintenance issues. First, if multiple specifications need to verify the same values then this can result in test code duplication. For example, two searches for a customer record with different criteria may be expected to return the same result. Second, when many fine-grained assertions are performed within a specification, the overall purpose can become obscured. For example, a specification may indicate that a value returned from an order process “should contain a first name and a last name and a home phone number and an address line 1 and …” while the intended perspective may be that the operation “should return a shipment confirmation”.

One solution to this problem is to override an object’s equality operators and/or methods to suit the needs of the test. Unfortunately, this is not without its own set of issues. Aside from introducing behavior into the system which is only exercised by the tests, this strategy may conflict with the existing or future needs of the system due to a difference in how each define equality for the objects being compared. While a test may need to compare all the properties of two objects, the system may require equality to be based upon the object’s identity (e.g. two customers are the same if they have the same customer Id). It may happen that the system already defines equality suitable to the needs of the test, but this is subject to change. A system may compare two objects by value for the purposes of indexing, ensuring cardinality, or an assortment of domain-specific reasons whose needs may change as the system evolves. While the initial state of an object’s definition of equality may coincide with the needs of the test, the needs of both represent two axes of change which could lead to higher maintenance costs if not dealt with separately.

When using state-based verification, one way of avoiding test code duplication, obscurity and the need to equip the system with test-specific equality code is to implement the Expected Object pattern. The Expected Object pattern defines objects which encapsulate test-specific equality separate from the objects they are compared against. An expected object may be implemented as a sub-type whose equality members have been overloaded to perform the desired comparisons or as a test-specific type designed to compare itself against another object type.

Consider the following specification which validates that placing an order returns an order receipt populated with the expected values:

[Subject(typeof (OrderService))]
public class when_an_order_is_placed : WithSubject<OrderService>
{
	static readonly Guid CustomerId = new Guid("061F3CED-405F-4261-AF8C-AA2B0694DAD8");
	const long OrderNumber = 1L;
	static Customer _customer;
	static Order _order;
	static OrderReceipt _orderReceipt;


	Establish context = () =>
		{
			_customer = new TestCustomer(CustomerId)
				            {
				            	FirstName = "First",
				            	LastName = "Last",
				            	PhoneNumber = "5129130000",
				            	Address = new Address
				            		        {
				            		          	LineOne = "123 Street",
				            		          	LineTwo = string.Empty,
				            		          	City = "Austin",
				            		          	State = "TX",
				            		          	ZipCode = "78717"
				            		        }
				            };
			For<IOrderNumberProvider<long>>().Setup(x => x.GetNext()).Returns(OrderNumber);
			For<ICustomerRepository>().Setup(x => x.Get(Parameter.IsAny<Guid>())).Returns(_customer);
			_order = new Order(1, "Product A");
		};

	Because of = () => _orderReceipt = Subject.PlaceOrder(_order, _customer.Id);

	It should_return_a_receipt_with_order_number = () => _orderReceipt.OrderNumber.ShouldEqual(OrderNumber.ToString());

	It should_return_a_receipt_with_order_description = () => _orderReceipt.Orders.ShouldContain(_order);

	It should_return_a_receipt_with_customer_id = () => _orderReceipt.CustomerId.ShouldEqual(_customer.Id.ToString());
		
	It should_return_an_order_receipt_with_customer_name = () => _orderReceipt.CustomerName.ShouldEqual(_customer.FirstName + " " + _customer.LastName);

	It should_return_a_receipt_with_customer_phone = () => _orderReceipt.CustomerPhone.ShouldEqual(_customer.PhoneNumber);

	It should_return_a_receipt_with_address_line_1 = () => _orderReceipt.AddressLineOne.ShouldEqual(_customer.Address.LineOne);

	It should_return_a_receipt_with_address_line_2 = () => _orderReceipt.AddressLineTwo.ShouldEqual(_customer.Address.LineTwo);
		
	It should_return_a_receipt_with_city = () => _orderReceipt.City.ShouldEqual(_customer.Address.City);

	It should_return_a_receipt_with_state = () => _orderReceipt.State.ShouldEqual(_customer.Address.State);

	It should_return_a_receipt_with_zip = () => _orderReceipt.ZipCode.ShouldEqual(_customer.Address.ZipCode);
}

Listing 1

While the specification in listing 1 provides ample detail about the values that should be present on the returned receipt, such an implementation precludes reuse and tends to overwhelm the purpose of the specification. This problem is further compounded as the composition complexity increases.

As an alternative to declaring what each field of a particular object should contain, the Expected Object pattern allows you to declare what a particular object should look like. By replacing the specification’s discrete assertions with a single assertion comparing an Expected Object against a resulting state, the essence of the specification can be preserved while maintaining an equivalent level of verification.

Consider the following simple implementation for an Expected Object:

class ExpectedOrderReceipt : OrderReceipt
{
	public override bool Equals(object obj)
	{
		var otherReceipt = obj as OrderReceipt;

		return OrderNumber.Equals(otherReceipt.OrderNumber) &&
			    CustomerId.Equals(otherReceipt.CustomerId) &&
			    CustomerName.Equals(otherReceipt.CustomerName) &&
			    CustomerPhone.Equals(otherReceipt.CustomerPhone) &&
			    AddressLineOne.Equals(otherReceipt.AddressLineOne) &&
			    AddressLineTwo.Equals(otherReceipt.AddressLineTwo) &&
			    City.Equals(otherReceipt.City) &&
			    State.Equals(otherReceipt.State) &&
			    ZipCode.Equals(otherReceipt.ZipCode) &&
			    Orders.ToList().SequenceEqual(otherReceipt.Orders);
	}
}

Listing 2

Establishing an instance of the expected object in listing 2 allows the previous discrete assertions to be replaced with a single assertion declaring what the returned receipt should look like:

[Subject(typeof (OrderService))]
public class when_an_order_is_placed : WithSubject<OrderService>
{
	const long OrderNumber = 1L;
	static readonly Guid CustomerId = new Guid("061F3CED-405F-4261-AF8C-AA2B0694DAD8");
	static Customer _customer;
	static ExpectedOrderReceipt _expectedOrderReceipt;
	static Order _order;
	static OrderReceipt _orderReceipt;


	Establish context = () =>
		{
			_customer = new TestCustomer(CustomerId)
				            {
				            	FirstName = "First",
				            	LastName = "Last",
				            	PhoneNumber = "5129130000",
				            	Address = new Address
				            		        {
				            		          	LineOne = "123 Street",
				            		          	LineTwo = string.Empty,
				            		          	City = "Austin",
				            		          	State = "TX",
				            		          	ZipCode = "78717"
				            		        }
				            };
			For<IOrderNumberProvider<long>>().Setup(x => x.GetNext()).Returns(OrderNumber);
			For<ICustomerRepository>().Setup(x => x.Get(Parameter.IsAny<Guid>())).Returns(_customer);
			_order = new Order(1, "Product A");

			_expectedOrderReceipt = new ExpectedOrderReceipt
				                        {
				                        	OrderNumber = OrderNumber.ToString(),
				                        	CustomerName = "First Last",
				                        	CustomerPhone = "5129130000",
				                        	AddressLineOne = "123 Street",
				                        	AddressLineTwo = string.Empty,
				                        	City = "Austin",
				                        	State = "TX",
				                        	ZipCode = "78717",
				                        	CustomerId = CustomerId.ToString(),
				                        	Orders = new List<Order> {_order}
				                        };
		};

	Because of = () => _orderReceipt = Subject.PlaceOrder(_order, _customer.Id);

	It should_return_an_receipt_with_shipping_information_and_order_number =
		() => _expectedOrderReceipt.Equals(_orderReceipt).ShouldBeTrue();
}

Listing 3

The implementation strategy in listing 3 offers a subtle shift in perspective, but one which may more closely model the language of the business.

This is not to say that discrete assertions are always wrong. The level of detail modeled by an application’s specifications should be based upon the needs of the business. Consider the test runner output for both implementations:

ExpectedObjectContrast

Figure 1

Examining the results of executing both specifications in figure 1, we see that the first describes each field being validated, while the second describes what the validations of these fields collectively mean. Which is best will depend upon your particular business needs. While the first implementation provides a more detailed specification of the receipt, this may or may not be as important to the business as knowing that the receipt as a whole is correct. For example, consider if the order number were missing. Is the correct perspective that the receipt is 90% correct or 100% wrong? The correct answer is … it depends.

Explicit Feedback

While the Expected Object implementation shown in listing 2 may be an adequate approach in some cases, it does have the shortcoming of not providing explicit feedback of how the two objects differ. To address this, we can implement our Expected Object as a Custom Assertion. Instead of asserting on the return value of comparing the expected object to an object returned from our system, we can design the Expected Object to throw an exception detailing what state differed between the two objects. The following listing demonstrates this approach:

class ExpectedOrderReceipt : OrderReceipt
{
	public void ShouldEqual(object obj)
	{
		var otherReceipt = obj as OrderReceipt;
		var messages = new List<string>();

		if (!OrderNumber.Equals(otherReceipt.OrderNumber))
			messages.Add(string.Format("For OrderNumber, expected '{0}' but found '{1}'", OrderNumber, otherReceipt.OrderNumber));

		if (!CustomerId.Equals(otherReceipt.CustomerId))
			messages.Add(string.Format("For CustomerId, expected '{0}' but found '{1}'", CustomerId, otherReceipt.CustomerId));

		if (!CustomerName.Equals(otherReceipt.CustomerName))
			messages.Add(string.Format("For CustomerName, expected '{0}' but found '{1}'", CustomerName, otherReceipt.CustomerName));

		if (!CustomerPhone.Equals(otherReceipt.CustomerPhone))
			messages.Add(string.Format("For CustomerPhone, expected '{0}' but found '{1}'", CustomerPhone, otherReceipt.CustomerPhone));

		if (!AddressLineOne.Equals(otherReceipt.AddressLineOne))
			messages.Add(string.Format("For AddressLineOne, expected '{0}' but found '{1}'", AddressLineOne, otherReceipt.AddressLineOne));

		if (!AddressLineTwo.Equals(otherReceipt.AddressLineTwo))
			messages.Add(string.Format("For AddressLineTwo, expected '{0}' but found '{1}'", AddressLineTwo, otherReceipt.AddressLineOne));

		if (!City.Equals(otherReceipt.City))
			messages.Add(string.Format("For City, expected '{0}' but found '{1}'", City, otherReceipt.City));

		if (!State.Equals(otherReceipt.State))
			messages.Add(string.Format("For State, expected '{0}' but found '{1}'", State, otherReceipt.State));

		if (!ZipCode.Equals(otherReceipt.ZipCode))
			messages.Add(string.Format("For ZipCode, expected '{0}' but found '{1}'", ZipCode, otherReceipt.ZipCode));

		if (!Orders.ToList().SequenceEqual(otherReceipt.Orders))
			messages.Add("For Orders, expected the same sequence but was different.");

		if(messages.Count > 0)
			throw new Exception(string.Join(Environment.NewLine, messages));
	}
}

Listing 4

The following listing shows the specification modified to use the new Expected Object implementation with several values on the TestCustomer modified to return values differing from the expected value:

[Subject(typeof (OrderService))]
public class when_an_order_is_placed : WithSubject<OrderService>
{
	const long OrderNumber = 1L;
	static readonly Guid CustomerId = new Guid("061F3CED-405F-4261-AF8C-AA2B0694DAD8");
	static Customer _customer;
	static ExpectedOrderReceipt _expectedOrderReceipt;
	static Order _order;
	static OrderReceipt _orderReceipt;


	Establish context = () =>
		{
			_customer = new TestCustomer(CustomerId)
				            {
				            	FirstName = "Wrong",
				            	LastName = "Wrong",
				            	PhoneNumber = "Wrong",
				            	Address = new Address
				            		        {
				            		          	LineOne = "Wrong",
				            		          	LineTwo = "Wrong",
				            		          	City = "Austin",
				            		          	State = "TX",
				            		          	ZipCode = "78717"
				            		        }
				            };
			For<IOrderNumberProvider<long>>().Setup(x => x.GetNext()).Returns(OrderNumber);
			For<ICustomerRepository>().Setup(x => x.Get(Parameter.IsAny<Guid>())).Returns(_customer);
			_order = new Order(1, "Product A");

			_expectedOrderReceipt = new ExpectedOrderReceipt
				                        {
				                        	OrderNumber = OrderNumber.ToString(),
				                        	CustomerName = "First Last",
				                        	CustomerPhone = "5129130000",
				                        	AddressLineOne = "123 Street",
				                        	AddressLineTwo = string.Empty,
				                        	City = "Austin",
				                        	State = "TX",
				                        	ZipCode = "78717",
				                        	CustomerId = CustomerId.ToString(),
				                        	Orders = new List<Order> {_order}
				                        };
		};

	Because of = () => _orderReceipt = Subject.PlaceOrder(_order, _customer.Id);

	It should_return_an_receipt_with_shipping_and_order_information = () => _expectedOrderReceipt.ShouldEqual(_orderReceipt);
}

Listing 5

Running the specification produces the following output:

ExpectedObjectExplicitFeedback
Figure 2

Conclusion

This time, we took a look at the Expected Object pattern which aids in reducing code duplication, eliminating the need to put test-specific equality behavior in our production code and serves as a strategy for further clarifying the intent of our specifications. Next time, we’ll look at some strategies for combating obscurity and test-code duplication caused by test data.

Tagged with: