Stub Proxy Generation Quick Reference

The Genome Testing Extensions library contains a framework that can be used to create stub instances of persistent classes to be used for unit tests. The created stubs are completely detached from any DataDomain or Context instances, so they behave like a value object or POCO. These stub objects can be used to provide sample domain entities for unit testing.

The compiled version of the extension pack can be found in the bin folder of the Genome installation, but it is also provided as source code (see {Genome installation folder}\Extensions\Testing). The stub proxy generation framework uses the Castle DynamicProxy library for proxy generation. This library is not included in the Genome installation, but you can download it from the Castle project website: http://www.castleproject.org/castle/download.html.

This chapter describes the behaviour of this framework in a short form. As the unit testing sample of the Genome installation ({Genome installation folder}\Samples\CSharp\UnitTesting) has been updated to use the stub proxy generation framework the majority of the examples in this document are taken from that sample.

Creating stub objects using the stub proxy generation framework

Required components for stub creation

·    The TechTalk.Genome.Extensions.Testing extension project contains all logic necessary for genome stub proxy generation.

·    The proxy generation code can be found in the TechTalk.Genome.Extensions.Testing.StubProxy namespace.

·    The proxy generation requires the already mentioned Castle DynamicProxyopen-source component (the assemblies Castle.Core and Castle.DynamicProxy).

·    The stub proxy generation was tested with the v2.1 of the DynamicProxy.

Creating stubs

·    New stubs can be created using the ObjectStubFactory class:

Cusotmer customerStub = ObjectStubFactory.Create<Customer>();

·    Constructor parameters can also be provided for the create method.

·    The subs will implement the IPersistentStub interface which can be accessed by casting the stub, or though a helper extension method:

customerStub.AsStub()

Equality of stubs

·    The equality of stubs (Equals() method) is defined as reference equality (and not the primary key equality!)

·    If the equals (==) operator is not redefined (so the default, or the Persistent equals operator is used) this behaves also like reference equality

Constructors

·    The stub calls the constructor that matches best to the specified arguments (like DataDomain.New()).

·    It is recommended to have a default constructor.

·    There is no way to bypass the constructor.

Stub or not stub

·    If you need to decide in the business logic (especially in the persistent class itself), whether the actual object is a stub or not, you can test it against the TechTalk.Genome.IObjectProxy interface. The stubs will not implement this interface.

·    This can be useful to bypass any Genome related optimizations in the code (the ones that doesn't change the behaviour; otherwise your tests will be unreliable).

Stub properties

Abstract scalar/reference properties

·    The sub stores the value that was set, and returns it on get.

·    If the property was not set prior accessing, it returns the type default (null/0/etc.).

Virtual scalar/reference properties

·    If you did not set them though the stub, they fall-back on the original implementation.

·    If you have ever set the property through the stub, the getter returns the value that was set.

Read-only scalar/reference properties

·    You can still set these properties using one of the following methods

((IPersistentStub)order).SetProperty("OrderId", 42);

order.SetProperty(o => o.OrderId, 24); 

·    The SetProperty method is specified through the extension method on TechTalk.Genome.Extensions.Testing.StubProxy.ObjectStubExtensions.

Properties mapped with <Linq>

·    As the normal stub generation does not know about the DataDomain schema, these properties work like persistent fields (so you have to set the value through the stub).

Non-virtual properties

·    These properties cannot be influenced by the stub. They behave as they were implemented.

Custom actions on property set

·    You can subscribe to PropertySetInvoking/PropertySetInvoked events of the stub, to perform specific actions.

stub.AsStub().PropertySetInvoked += (sender, args) =>

    {

        Console.WriteLine("Property {0} was set to '{1}'", args.PropertyName, args.Value);

    };

·    The event is triggered by the standard property sets only. The initialization of the properties and the property sets through the SetProperty helper method will not trigger the event.

Stub collections

Collections

·    The stub generator creates a stub collection instance for the collection properties that is preserved (multiple get calls return the same always).

·    You can call the Add/Remove/Clean method of the stub collection to initialize the items.

·    The stub does not know about the schema, so it will not know whether the collection was mapped as 1-n or n-m collection.

Sort order of collections

·    If the tested logic depends on the default sort order of the collection, you can specify it for the sub collection as well, like:

customerStub.Orders.AsStub().SortOrder = (ox, oy) => decimal.Compare(ox.Freight.Value, oy.Freight.Value);

Custom actions on collection add/remove

·    You can subscribe to ItemAdded/ItemRemoved events of the stub collection, to perform specific actions.

stub.Orders.AsStub().ItemAdded += (collection, args) =>

    {

      args.Item.Customer = (Customer)args.ParentObject;

    };

·    The Clear() method on the collection will trigger the ItemRemoved event for each item in the collection.

1-n collection parent reference update

·    You can configure a stub collection in one step to behave like a 1-n collection (that updates the parent reference on add/remove):

customerStub.Orders.SetupAsOneToManyCollection(o => o.Customer);

·    You have to specify an expression that returns the parent reference.

·    You can optionally also define the sort order.

·    Notes:

o    This call will not enable the parent reference tracking (i.e. when the parent reference is set by the logic, this will not be added to the collection automatically).

o    This call will not track the deleted items (i.e. when an item is deleted by the logic, it will not be removed from the collection).

Many-to-many collection reverse collection update

·    You can configure a stub collection in one step to behave like a n-m collection (that updates the reverse collection on add/remove):

employeeStub.Territories.SetupAsManyToManyCollection(t=> t.Employees);

·    You have to specify an expression that returns the reverse collection.

·    You can optionally also define the sort order.

·    Notes:

o    This call will not track the deleted items (i.e. when an item is deleted by the logic, it will not be removed from the collection).

o    This call will not track create/delete operations of the association table entries (i.e. when an association table entry is created/deleted by the logic, the related items will not be added/removed from the collection).

Stub sets

Static set stubs

·    The stub generator creates a stub set instance for the set properties, that is preserved (multiple get calls return the same always)

·    You can call the Add/AddRange/Insert/Remove/Clean methods of the stub set to initialize the items. For that you need to cast the Set<T> to SetStub<T>, that can be done by the AsStub() helper extension method:

stub.CustomerSet.AsStub().Add(customerStub);

·    The stub does not know about the schema, so it will not know how it was mapped. On the stub, it will always act as a static set.

Dynamic set stubs

·    You can also setup a set property as "dynamic" set stub. For the dynamic set stub a lambda can be provided that returns the actual result of the set.

stub.CustomerSet.SetupAsDynamicSetStub(() => GetActualCustomerCollection());