Home > Uncategorized > Object Mothers as Fakes

Object Mothers as Fakes

Update: I was in a pairing session and we decided that that this syntax is way too noisy. So we have written it here. Effectively to a.Banner.isValid().with(new{Name="john"})

This is a follow to Fakes Arent Object Mothers Or Test Builders. Over the last few weeks I have had the chance to reengage with MO and fakes. I am going to document a helper or two with building fakes using extensions. But before that I want to make an observation or two:

  • I have been naming my mother objects as ValidMO.xxxxx and making sure they are static
  • I find that I have a ValidMO per project and that is better than a shared one. For example, in my unit tests the validMO tends to need Ids populated and that is really good as I explore the domain and have to build up the object graph. In my integration tests, however, Id shouldn’t be populated because that comes from the database and of course changes over time. My MOs in the regression and acceptance test are different again. I wouldn’t have expected this. It is great because I don’t have to spawn yet another project just to hold the MO data as I have in previous projects.
  • I find that I only need one MO per object
  • Needing another valid example suggests in fact a new type of domain object
  • I don’t need to have invalid MOs (I used to)

Now for some code.

When building objects for test, the pattern is to either:

  • provide the MO
  • build the exceptions in a new object and fill out the rest with the MO
  • not use MO at all (very rarely)
  • I use this pattern to build objects with basic objects and not Lists – you’ll need to extend the tests/code for that

To do this I primarily use a builder as an extension. This extension differs between unit and integration tests.

Unit test object builder

This code has two tests: Valid and Invalid. The tests demonstrate a couple of things. The first of which is that I am bad by having three tests in the first test. Let’s just say that’s for readability ;-) The first set of tests check that I have setup the ValidMO correctly and that it behaves well with the extension helper “SetupWithDefaultValuesFrom”. This series of tests has been really important in the development of the extensions and quickly point to problems – and believe me this approach hits limits quickly. Nonetheless it is great for most POCOs.

The second test demonstrates testing invalid by exception. Here there rule is that the image banner is invalid if it has no name. So the code says, make the name empty and populate the rest of the model.

  using Core.Domain.Model;
  using Contrib.TestHelper;
  using Microsoft.VisualStudio.TestTools.UnitTesting;
  using UnitTests.Core.Domain.MotherObjects;
  using NBehave.Spec.MSTest;

  namespace UnitTests.Core.Domain.Model
  {
      /// <summary>
      /// Summary description for Image Banner Property Validation Test
      /// </summary>
      [TestClass]
      public class ImageBannerPropertyValidationTest
      {
          [TestMethod]
          public void Valid()
          {
              ValidMO.ImageBanner.ShouldNotBeNull();
              ValidMO.ImageBanner.IsValid().ShouldBeTrue();
              new ImageBanner().SetupWithDefaultValuesFrom(ValidMO.ImageBanner).IsValid().ShouldBeTrue();
          }

          [TestMethod]
          public void InvalidHasNoName()
          {
              new ImageBanner { Name = "" }.SetupWithDefaultValuesFrom(ValidMO.ImageBanner).IsValid().ShouldBeFalse();
          }
      } 
  }

Here’s the ValidMO.

  namespace UnitTests.Core.Domain.MotherObjects
  {
      public static class ValidMO
      {
          public static IBanner ImageBanner
          {
              get
              {
                  return new ImageBanner
                             {
                                 Id = 1,
                                 Name = "KiwiSaver",
                                 Url = "http://localhost/repos/first-image.png",
                                 Destination = "http://going-to-after-click.com",
                                 Description = "Kiwisaver Banner for latest Govt initiative",
                                 IsActive = true,
                                 IsDeleted = false
                             };
              }
          }
      }
  }

At this stage, you should have a reasonable grasp of the object through the tests and what we think is exemplar scenario data. Here’s the model if you insist.

  using Castle.Components.Validator;

  namespace Core.Domain.Model
  {
      public class ImageBanner : Banner
      {
          [ValidateNonEmpty, ValidateLength(0, 200)]
          public virtual string Url { get; set; }

          [ValidateNonEmpty, ValidateLength(0, 200)]
          public string Destination { get; set; }
      }
  }

Builder extensions

Now for the extensions that hold this code together. It is a simple piece of code that takes the model and OM and merges them. Before we go too far. It is actually little more ugly than I want it to be and haven’t had time to think of a more elegant solution. There are actually two methods. One that overrides the model based on null/empty properties and one that also see zero ints and empty strings also as null/empty. So generally, you need to two.

  using System;
  using System.Linq;
  using Contrib.Utility;

  namespace Contrib.TestHelper
  {
      /// <summary>
      /// Test helpers on Mother objects
      /// </summary>
      public static class MotherObjectExtensions
      {

          /// <summary>
          /// Setups the specified model ready for test by merging a fake model with the model at hand. The code merges
          /// the properties of the given model with any defaults from the fake. If the value of a property on the model is an int and its value is 0 
          /// it is treated as null and the fake is used instead.
          /// overridden 
          /// </summary>
          /// <typeparam name="T"></typeparam>
          /// <param name="model">The model.</param>
          /// <param name="fake">The fake.</param>
          /// <returns>the model</returns>
          public static T SetupWithDefaultValuesFrom<T>(this T model, T fake)
          {
              var props = from prop in model.GetType().GetProperties() // select model properities to populate the fake because the fake is the actual base
                          where prop.CanWrite
                          && prop.GetValue(model, null) != null
                          && (
                              ((prop.PropertyType == typeof(int) || prop.PropertyType == typeof(int?)) && prop.GetValue(model, null).As<int>() != 0)
                           || ((prop.PropertyType == typeof(long) || prop.PropertyType == typeof(long?)) && prop.GetValue(model, null).As<long>() != 0)
                           || (prop.PropertyType == typeof(string) && prop.GetValue(model, null).As<string>() != String.Empty)
                              )
                          select prop;

              foreach (var prop in props)
                  prop.SetValue(fake, prop.GetValue(model, null), null); //override the fake with model values

              return fake;
          }
   
          /// <summary>
          /// Setups the specified model ready for test by merging a fake model with the model at hand. The code merges
          /// the properties of the given model with any defaults from the fake. This method is the same as <see cref="SetupWithDefaultValuesFrom{T}"/>
          /// except that empty strings or 0 int/long are able to be part of the setup model
          /// overridden 
          /// </summary>
          /// <typeparam name="T"></typeparam>
          /// <param name="model">The model.</param>
          /// <param name="fake">The fake.</param>
          /// <returns>the model</returns>
          public static T SetupWithDefaultValuesFromAllowEmpty<T>(this T model, T fake)
          {
              var props = from prop in model.GetType().GetProperties() // select model properities to populate the fake because the fake is the actual base
                          where prop.CanWrite
                          && prop.GetValue(model, null) != null
                          select prop;

              foreach (var prop in props)
                  prop.SetValue(fake, prop.GetValue(model, null), null); //override the fake with model values

              return fake;
          }
      }
  }

oh, and I just noticed there is another little helper in there too. It is the object.As helper that casts my results in this case as a string. I will include it and thank Rob for the original code and Mark for an update – and probably our employer for sponsoring our after-hours work ;-) :

    using System;
    using System.IO;


    namespace Contrib.Utility
    {
        /// <summary>
        /// Class used for type conversion related extension methods
        /// </summary>
        public static class ConversionExtensions
        {
            public static T As<T>(this object obj) where T : IConvertible
            {
                return obj.As<T>(default(T));
            }

            public static T As<T>(this object obj, T defaultValue) where T : IConvertible
            {
                try
                {
                    string s = obj == null ? null : obj.ToString();
                    if (s != null)
                    {
                        Type type = typeof(T);
                        bool isEnum = typeof(Enum).IsAssignableFrom(type);
                        return (T)(isEnum ?
                            Enum.Parse(type, s, true)
                            : Convert.ChangeType(s, type));
                    }
                }
                catch
                {
                }
                return defaultValue; 
            }

            public static T? AsNullable<T>(this object obj) where T : struct, IConvertible
            {
                try
                {
                    string s = obj as string;
                    if (s != null)
                    {
                        Type type = typeof(T);
                        bool isEnum = typeof(Enum).IsAssignableFrom(type);
                        return (T)(isEnum ?
                            Enum.Parse(type, s, true)
                            : Convert.ChangeType(s, type));
                    }
                }
                catch
                {

                }
                return null;
            }

            public static byte[] ToBytes(this Stream stream)
            {
                int capacity = stream.CanSeek ? (int)stream.Length : 0;
                using (MemoryStream output = new MemoryStream(capacity))
                {
                    int readLength;
                    byte[] buffer = new byte[4096];

                    do
                    {
                        readLength = stream.Read(buffer, 0, buffer.Length);
                        output.Write(buffer, 0, readLength);
                    }
                    while (readLength != 0);

                    return output.ToArray();
                }
            }

            public static string ToUTF8String(this byte[] bytes)
            {
                if (bytes == null)
                    return null;
                else if (bytes.Length == 0)
                    return string.Empty;
                var str = System.Text.Encoding.UTF8.GetString(bytes);

                // If the string begins with the byte order mark
                if (str[0] == '\xFEFF')
                    return str.Substring(1);
                else
                {
                    return str;
                }
            }
        }
    }
    

Finally, if actually do use this code here are tests:

  using Contrib.TestHelper;
  using Microsoft.VisualStudio.TestTools.UnitTesting;
  using NBehave.Spec.MSTest;

  namespace Contrib.Tests.TestHelper
  {
      /// <summary>
      /// Summary description for MotherObject Extensions Test
      /// </summary>
      [TestClass]
      public class MotherObjectExtensionsTest
      {

          [TestMethod]
          public void EmptyString()
          {
              var test = new Test { }.SetupWithDefaultValuesFrom(new Test { EmptyString = "this" });
              test.EmptyString.ShouldEqual("this");
          }
          [TestMethod]
          public void ModelStringIsUsed()
          {
              var test = new Test { EmptyString = "that" }.SetupWithDefaultValuesFrom(new Test { EmptyString = "this" });
              test.EmptyString.ShouldEqual("that");
          }
          [TestMethod]
          public void EmptyStringIsAccepted()
          {
              var test = new Test { EmptyString = "" }.SetupWithDefaultValuesFromAllowEmpty(new Test { EmptyString = "this" });
              test.EmptyString.ShouldEqual("");
          }

          [TestMethod]
          public void ZeroInt()
          {
              var test = new Test { }.SetupWithDefaultValuesFrom(new Test { ZeroInt = 1 });
              test.ZeroInt.ShouldEqual(1);
          }
          [TestMethod]
          public void ModelIntIsUsed()
          {
              var test = new Test { ZeroInt = 2 }.SetupWithDefaultValuesFrom(new Test { ZeroInt = 1 });
              test.ZeroInt.ShouldEqual(2);
          }
          [TestMethod]
          public void ZeroIntIsAccpted()
          {
              var test = new Test { ZeroInt = 0}.SetupWithDefaultValuesFromAllowEmpty(new Test { ZeroInt = 1 });
              test.ZeroInt.ShouldEqual(0);
          }

          [TestMethod]
          public void ZeroLong()
          {
              var test = new Test { }.SetupWithDefaultValuesFrom(new Test { ZeroLong = 1 });
              test.ZeroLong.ShouldEqual(1);
          }
          [TestMethod]
          public void ModelLongIsUsed()
          {
              var test = new Test { ZeroLong = 2 }.SetupWithDefaultValuesFrom(new Test { ZeroLong = 1 });
              test.ZeroLong.ShouldEqual(2);
          }
          [TestMethod]
          public void ZeroLongIsAccepted()
          {
              var test = new Test {ZeroLong = 0}.SetupWithDefaultValuesFromAllowEmpty(new Test { ZeroLong = 1 });
              test.ZeroLong.ShouldEqual(0);
          }

          private class Test
          {
              public string EmptyString { get; set; }
              public int ZeroInt { get; set; }
              public long ZeroLong { get; set; }
          }
      }
  }

I hope that helps.