Compartir vía


Using Stubs

When you use a stub, the Moles framework generates stub implementations of dependency methods and classes for unit testing. This functionality is similar to that of many conventional isolation frameworks such as Moq, NMock2, and Rhino Mocks. The stub types are automatically generated classes that run very quickly at execution and are simple to use. However, stubs lack many of the capabilities of the conventional mocking frameworks and may require some additional coding. Moles can automatically generate stub types for both for your own code and for third-party assemblies you are using, including assemblies in the .NET Framework or SharePoint. By default, stubs are generated for all interfaces and abstract classes, although you can also configure Moles to generate stubs for non-abstract classes that expose virtual methods. You can configure stub generation through a stub configuration file. The following code example shows the stub configuration file for the Microsoft.Practices.SharePoint.Common assembly.

<?xml version="1.0" encoding="utf-8" ?>
<Moles xmlns="https://schemas.microsoft.com/moles/2010/" Verbosity="Noisy">
  <Assembly Name="Microsoft.Practices.SharePoint.Common"/>
  <StubGeneration>
    <TypeFilter NonSealedClasses="true" Namespace="Microsoft*" />
  </StubGeneration>
  <MoleGeneration Disable="false" />
  <Compilation Disable="true" />
</Moles>

This configuration file instructs the Moles framework to generate stub implementations for all non-sealed classes in namespaces that begin with "Microsoft" within the Microsoft.Practices.SharePoint.Common assembly. Note that in some cases, it may be easier to manually implement mocks or stubs for your own code instead of using the stub class generated by the Moles framework.

The following example shows how to consume a stub object generated by the Moles framework within a test class. In this example, we want to test the ServiceLocatorConfig class, a key component of the SharePoint Service Locator. The ServiceLocatorConfig class depends on implementations of the IConfigManager interface to manage the storage of configuration settings. In this case, the IConfigManager instance is provided by a stub implementation named SIConfigManager.

[TestMethod]
public void SetSiteCacheInterval_WithValidValue_UpdatesConfiguration()
{
  // Arrange
  int expected = 30;
  string expectedKey = 
    "Microsoft.Practices.SharePoint.Common.SiteLocatorCacheInterval";
  var bag = new BIPropertyBag();
  int target = -1;

  var cfgMgr = new SIConfigManager();
  cfgMgr.SetInPropertyBagStringObjectIPropertyBag = 
    (key, value, propBag) =>
    {
      if(key == expectedKey)
        target = (int) value;
    };
  
  cfgMgr.GetPropertyBagConfigLevel = (configlevel) => bag;

  var config = new ServiceLocatorConfig(cfgMgr);

  // Act
  config.SetSiteCacheInterval(expected);

  // Assert
  Assert.AreEqual(expected, target);
}

There are a few key points you need to be aware of to fully understand this test method. First, the following describes the naming conventions used for the generated stubs.

  • The generated stub class, SIConfigManager, provides stubs for the interface IConfigManager. The naming convention precedes the name of the interface or abstract class with the letter "S".
  • The stub class is created in the Microsoft.Practices.SharePoint.Common.Configuration.Moles namespace. Stub classes are created in a sub-namespace, .Moles, of the namespace that contains the interface or abstract class being stubbed. In this case, the interface in question, IConfigManager, is in the Microsoft.Practices.SharePoint.Common.Configuration namespace.

The Arrange section of the code example is the setup phase of the test. You can see that the stub object is passed in to the constructor of the class under test, ServiceLocatorConfig, which requires an argument of type IConfigManager. This is an example of constructor injection, which is a type of dependency injection. Whenever you use a fake object, you need a way to provide the fake object to the code under test, and you typically do this by using some form of dependency injection.

So how do we make our stub object instance simulate the behavior we require for the unit test? One of the key tenets of the Moles framework is that you can override virtual methods or interface methods in a flexible way, by attaching delegates to the corresponding method in the stub class. When the object under test calls interface methods on the stub object, the stub object will invoke our delegate. In the Arrange section, you can see that lambda expressions are used to specify implementations for two delegates, SetInPropertyBagStringObjectIPropertyBag, and GetPropertyBagConfigLevel. The following conventions and approaches are used when defining delegate test implementations for a stub class:

  • The name of each delegate on the stub class indicates the name of the method on the interface, together with the parameters that it takes. This naming convention is intended to make the name of the delegate unique—a method may have multiple overloads, so adding parameter types makes the delegate name specific to an individual method overload. In the first example, the method name is SetInPropertyBag and the parameter types are String, Object, and IPropertyBag. Hence the delegate is named SetInPropertyBagStringObjectIPropertyBag.
  • Each lambda expression defines an anonymous method that will be invoked by our delegate. The stub class invokes this delegate when the code under test calls the corresponding actual method on the interface.
  • The following code shows the first delegate implementation for the SIConfigManager stub class:
cfgMgr.SetInPropertyBagStringObjectIPropertyBag = 
  (key, value, propBag) =>
  {
    if(key == expectedKey)
      target = (int) value;
  };

This example specifies the logic to invoke when the IConfigManager.SetInPropertyBag(string, object, IPropertyBag) method is called. If the provided key matches the expected key, then the value is saved in the local integer variable named target. If the provided key does not match the expected key, no action is taken. Reading and writing local variables within our lambda expressions provides a convenient way to record what occurs during the test, and allows us to check the values during the Assert phase of the test.

The following code shows the second delegate implementation for the SIConfigManager stub class:

cfgMgr.GetPropertyBagConfigLevel = (configlevel) => bag;

This example specifices the behavior for the method GetPropertyBag(ConfigLevel) and will always return a reference to the local variable named bag. A common mistake is to return a new value every time a lambda expression is evaluated. Often the code under test will expect the same value to be returned, and by defining a local variable you can ensure that the object is created once and the same value returned each time the test code is invoked.

Note

The bag local variable is a instance of BIPropertyBag, which is an example of another type of fake object known as a behaved type. For more information on behaved types, see Behavioral Models.

You can configure the Moles framework to respond in various ways if the code under test calls a stub method for which a test implementation has not been defined. By default, the framework will throw an exception indicating that the method has not been defined:

Microsoft.Moles.Framework.Behaviors.BehaviorNotImplementedException: SIConfigManager.global::Microsoft.Practices.SharePoint.Common.Configuration.IConfigManager.GetPropertyBag(ConfigLevel) was not stubbed.

A common approach to discovering which methods you need to stub for your test is to run the test, see if the exception is thrown for a missing stub method, then implement the stub method. The Moles framework also supports a BehaveAsDefault approach, in which case any stub methods that you have not implemented will return a default value for the return type of the method.

The remainder of the test class uses the same approach as any other unit test. The Act section performs one more action on the code that you want to test. The Assert section verifies that the code under test behaved as expected.