Unit tests - refactor to share data between tests in C#

2k views Asked by At

I am currently trying to refactor some unit tests, because they contain too much hard-coded data. The class structure is the following:

public class BaseClassValidator {
   [Test]
   public void TestAddress(){
     Address a = GetAddress();
     if (a != null){
        // verify and validate here
        Assert.AreEqual(..., ...);
     }
   }

   [Test]
   public void TestPhone(){
       // almost the same as above, with GetPhone();
   }

   // ... other public methods here ...

   protected Address GetAddress(){
     return null;
   }

   protected Phone GetPhone(){
     return null;
   }
}

public class US: BaseClassValidator{
    protected override Address GetAddress(){
       Address address = new Address();
       address.FirstName = "firstname";
       // fill in the address data ...
       return address;           
    }

    // override GetPhone as well and other protected methods
}

and a lot of other classes used for the same purpose - verify that a country address is valid. Every subclass must override the methods to get the test data and as you can imagine, this makes the file bigger and bigger with input data that in my opinion could be fetched/set differently. Further, some of these sub classes define specific test members to check the address is valid or not valid, with several checks, like:

public class US: BaseClassValidator {

    // override the method GetAddress, as explained above ...

    // define a new test method here...
    [Test]
    public void TestValidator {
        Address a = GetAddress();
        a.State = "KS";
        // validate the address
        // and assert that we get an error because state is wrong

        Address a = GetAddress();
        a.street = "";
        // validate and assert that we get an error because street is wrong


        // and so on...
    }

}

I was wondering if there is a better way to do this, as it seems that we are partially using the base class to test some data, but for something more specific we are using the sub classes - it can make sense, but then it seems we lose the benefit of using a template pattern like above. What I don't like is the way the data is created, because with a different approach, like by using data driven tests, we could read test data from a file (e.g., xml).

Is there a better way to share data between tests? How do you deal with this kind of situations?

2

There are 2 answers

2
Gerrie Schenck On

I am currently working on something similar and have introduced a Data class in my project. This simply contains a bunch of static data. It's a partial class so I can structure it a bit. I have a DataOrder.cs, DataOrderLine.cs and so on. Then there's public property for example Orders, which I can use in my test to get my test data.

Don't put all the data in base classes, this will be harder to maintain.

0
Ilya Palkin On

I was wondering if there is a better way to do this, as it seems that we are partially using the base class to test some data, but for something more specific we are using the sub classes - it can make sense, but then it seems we lose the benefit of using a template pattern like above.

People tend to use Composition over inheritance that's why usage of sub classes is not the best solution for unit testing.

Have you tried Object Mother or Test Data Builder patterns? They can be used to build data for your tests easily.

There are examples of Test Data Builder usages

var defaultAddres = new AddressBuilder().Build();
var addresWithState = new AddressBuilder().WithState("MyState").Build();
var addresWithStreet2 = new AddressBuilder().WithStreet2("street 2 value").Build();

and its simple implementation

public class AddressBuilder
{
    string state = "default STATE";
    string street1 = "default street 1";
    string street2 = "";

    public Address Build()
    {
        return new Address
        {
            State = state,
            Street1 = street1,
            Street2 = street2
        };
    }

    public AddressBuilder WithState(string value)
    {
        state = value;
        return this;
    }

    public AddressBuilder WithStreet1(string value)
    {
        street1 = value;
        return this;
    }

    public AddressBuilder WithStreet2(string value)
    {
        street2 = value;
        return this;
    }
}