Skip to content

Getting Started

Gary Webb edited this page Sep 20, 2018 · 6 revisions

To start using CherryPicker you will need:

  • An application
  • A test project to use your Cherry Picked objects in
  • A set of default data

To integrate CherryPicker into your tests, you will need to:

  1. Build a centrally stored container - available to all your tests
  2. Populate the container with the set of default data
  3. Use the container to build fully populated test data objects
  4. Override the set of default data to build objects specific to your test requirements

Let's look at each of these steps in more detail.

Building and Populating a Container

The TestDataContainer is the main class in CherryPicker. It has two responsibilities: to store the default data and to build new instances of data objects. We usually have one centrally located container that is used as the primary store for the defaults, though this is only a recommendation, you are able to build as many different containers as you wish and they will all work independently.

So first, we build an abstract base class which we will then inherit from in each of our test classes:

public abstract class CherryPickerTest
{
    //Build the container for default property values
    protected static ITestDataContainer testDataContainer = new TestDataContainer();

    static CherryPickerTest()
    {
        InitialiseTestDataTypesDefaults();
    }

    private static void InitialiseTestDataTypesDefaults()
    {
        //Fill in all data class's default property values
        testDataContainer
            .For<Person>(x => x
                .Default(p => p.FirstName).To("Albert")
                .Default(p => p.LastName).To("Einstein")
                .Default(p => p.Email).To("albert@emc2.com") 
                .Default(p => p.DOB).To(new DateTime(1879, 03, 14)))
            .For<Vehicle>(x => x
                .Default(v => v.Make).To("BMW")
                .Default(v => v.Model).To("3 Series"))
            .For<Address>(x => x
                .Default(a => a.FirstLine).To("221b Baker Street")
                //Cut short for brevity...
                .Default(a => a.PostCode).To("NW1 3RX"));
    }

    protected virtual T A<T>(params Action<DefaultOverride<T>>[] defaultOverrides)
    {
        return testDataContainer.Build(defaultOverrides);
    }

    protected virtual T An<T>(params Action<DefaultOverride<T>>[] defaultOverrides)
    {
        return testDataContainer.Build(defaultOverrides);
    }
}

You may have noticed a couple of extra helper methods at the end of the base class. These are used to make our tests even more beautiful, though they are not essential and you can exclude them. We will see examples with and without use of these methods below.

Building our First Test Data Object

Building new instances of our data objects is as simple as calling the Build method on the container. You can build as many instances of each type as you like; each call will create a new instance, using the same set of default data each time. Later we will see how to change the default data used for each new instance.

We now build our very first tests using CherryPicker:

public class HelloWorldTests : CherryPickerTest
{
    //We can use the TestDataContainer directly when building data objects, or...
    [Fact]
    public void HappyPathTest()
    {
        Person person = testDataContainer.Build<Person>();

        Assert.True(person.FirstName == "Albert");
        //All other properties of the Person object have also been filled in
    }

    //Use the helper methods A and An to make the tests much more readable
    [Fact]
    public void HappyPathTest2()
    {
        Person person = A<Person>();

        Assert.True(person.FirstName == "Albert");
        //All other properties of the Person object have also been filled in
    }
}

Building Unique Objects with Minimal Code

So, we're now able to build fully populated data objects with one line of code. The next step is to build fully populated data objects, but, with one or more values different to the defaults. We do this by passing in 'overrides' when building a new object. The overrides will not change the underlying defaults.

Let's add another test to our test class above:

[Fact]
public void UniqueObjectsWithMinimalCodeTest()
{
    //Pass in all overrides specific to this instance of the data object
    var albertsSister = A<Person>(x => x
        .Set(p => p.FirstName).To("Maja")
        .Set(p => p.DOB).To(new DateTime(1881, 11, 18)));

    //The underlying defaults are not changed by the overrides
    var albertEinstein = A<Person>();

    Assert.True(albertsSister.FirstName == "Maja");
    Assert.True(albertEinstein.FirstName == "Albert");
}

Addendum: Reference Type Properties

There are two ways of setting a reference type property in CherryPicker. The first is to set it directly as a default. CherryPicker would then use that instance of the reference type always (unless overridden) when building new instances:

As we did above, let's add more tests to our burgeoning test suite:

[Fact]
public void DirectlySetRefTypeTest()
{
    //A child container will inherit all defaults set in its parent, but any 
    //changes to the defaults will not be reflected back in its parent.
    var refTestDataContainer = testDataContainer.CreateChildContainer();
    refTestDataContainer
        .For<Person>(x => x
            .Default(p => p.Address).To(new Address { FirstLine = "112 Mercer Street" }));

    var albert1 = refTestDataContainer.Build<Person>();
    var albert2 = refTestDataContainer.Build<Person>();

    Assert.Same(albert1.Address, albert2.Address);
}

The second method is to let CherryPicker do the work for you. This is done by leaving the reference type property without a default. Using the example above, CherryPicker will create a new Address instance using the Address defaults whenever a new Person data object is built:

One last test for our suite:

[Fact]
public void RefTypeTest2()
{
    var albert1 = testDataContainer.Build<Person>();
    var albert2 = testDataContainer.Build<Person>();

    Assert.NotSame(albert1.Address, albert2.Address);
    Assert.True(albert1.Address.FirstLine == "221b Baker Street");
    Assert.True(albert2.Address.FirstLine == "221b Baker Street");
}

Next Steps