Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Methods for Adding Data to Mock DBs in C# Unit Tests

This post is meant to be more a discussion-starter, as I am somewhat new to unit testing and TDD.

I am currently writing some unit tests for a .NET process that interacts with several databases, and am using mock database contexts in an attempt to cover different edge cases within my tests, verify exception handling in the program itself, among other things. That being said, some of my unit tests use valid data, while others do not.

I am looking for feedback in terms of suggested best practices when adding valid/fake data to your mock database contexts. I've seen people do this a number of ways (e.g. - implement repository pattern, adding mock data to .csv files and making them part of the project, etc...).

I'm currently thinking about using a repository pattern for adding Survey objects to the Surveys table in my target DB.

First off, I've got the interface:

public interface ISurveyRepository
{
   IQueryable<Survey> SurveySeries { get; }
}

This is implemented both for the mocking fake/valid data repositories as needed by unit tests

class FakeSurveyRepository : ISurveyRepository
{
   private static IQueryable<Survey> fakeSurveySeries = new List<Survey> {
      new Survey { id = 1, SurveyName="NotValid1", SurveyData="<data>fake</data>"},
      new Survey { id = 2, SurveyName="NotValid2", SurveyData="<data>super fake</data>"},
      .........,
      new Survey {id = 10, SurveyName="NotValid10", SurveyData="<data>the fakest</data>" }       
   }.AsQueryable();

   public IQueryable<Survey> SurveySeries 
   { 
      get { return fakeSurveySeries; }
   }
}
// RealSurveyRepository : ISurveyRepository is similar to this, but with "good" data

I then have a class to consume this data for either fake/valid data by being passed a reference to the series in the constructor:

public class SurveySeriesProcessor
{
   private ISurveyRepository surveyRepository;

   public SurveySeriesProcessor( ISurveyRepository surveyRepository )
   {
       this.surveyRepository = surveyRepository;
   }

   public IQueryable<Survey> GetSurveys()
   {
      return surveyRepository.SurveySeries
   }
} 

And can then approach using these objects in my tests such as:

[TestClass]
public class SurveyTests
{
    [TestMethod]
    WhenInvalidSurveysFound_SurveyCopierThrowsInvalidSurveyDataErrorForEach()
    {
       // create mocking DB context and add fake data
       var contextFactory = new ContextFactory( ContextType.Mocking );
       var surveySeriesProcessor = new SurveySeriesProcessor( new FakeSurveyRepository() );

       foreach(Survey surveyRecord in surveySeriesProcessor.GetSurveys() )
       {
          contextFactory.TargetDBContext.Surveys.AddObject( surveyRecord );
       }
       // instantiate object being tested and run it against fake test data
       var testSurveyCopier = new SurveyCopier( contextFactory );
       testSurveyCopier.Start();
       // test behavior
       List<ErrorMessage> errors = testSurveyCopier.ErrorMessages;
       errors.Count.ShouldEqual( surveySeriesProcessor.GetSurveys().Count );
       foreach(ErrorMessage errMsg in errors)
       {
          errMsg.ErrorCode.ShouldEqual(ErrorMessage.ErrorMessageCode.InvalidSurveyData);
       }
    }
}

NOTE: I realize that in the example code provided I don't necessarily need to make the classes implementing ISurveyRepository return the series as an IQueryable<Survey> (they could very well be List<Survey>). However, I am going to extend the functionality of the interface and these classes in the future to filter out the fake/valid series based on certain criteria added to LINQ queries, which is why I made the repositories implement IQueryable<>. This is mock-up code designed to convey the basic principles of what I'm thinking.

With all of this in mind, what I'm asking is:

  1. Do you have any suggestions in terms of alternative approaches I could take in such scenarios?
  2. What methods have you employed in the past, what did you like/not like about them? Which have you found were the easiest to maintain?
  3. Given what I've posted, do you notice flaws in my general approach to unit testing? Sometimes I feel as though I write unit tests that attempt to cover too much ground instead of being concise, elegant, and to-the-point.

This is meant to be somewhat of an open discussion. Please keep in mind, this is the first set of unit tests I've ever written (I've read a decent amount of literature on the subject, however).

like image 558
Eric Stallcup Avatar asked Feb 18 '23 12:02

Eric Stallcup


1 Answers

I think you're on a good track.

Personally, in the same situation, if I were dealing with a repository style pattern,

public interface IRepository<T>
{
    IEnumerable<T> GetAll();
}


public class PonyRepository : IRepository<Pony>
{
    IEnumerable<Pony> GetAll();
}

To actually supply me the data I need, I generally create a TestObjects or TestFakes class to supply the required data on-demand.

public class FakeStuff
{
     public static IEnumerable<Pony> JustSomeGenericPonies(int numberOfPonies)
     {
        // return just some basic list
         return new List<Pony>{new Pony{Colour = "Brown", Awesomeness = AwesomenessLevel.Max}};

         // or could equally just go bananas in here and do stuff like...
         var lOfP = new List<Pony>();
         for(int i = 0; i < numberOfPonies; i++)
         {
             var p = new Pony();
             if(i % 2 == 0) 
             {
                 p.Colour = "Gray";
             }
             else
             {
                 p.Colour = "Orange"; 
             }

             lOfP.Add(p);
         }

         return lOfP;
     }
}

And test with this as such:

[Test]
public void Hello_I_Want_to_test_ponies()
{
    Mock<IRepository<Pony> _mockPonyRepo = new Mock<IRepository<Pony>>();
    _mockPonyRepo.SetUp(m => m.GetAll()).Returns(FakeStuff.JustSomeGenericPonies(50));

    // Do things that test using the repository
}

So this delivers reusability of the fake data, by keeping it out of the repository and in a place of it's own, meaning I can call this list of ponies anywhere a test requires a list of ponies, not just where a repository is involved.

If I need specific data for a specific testcase, i'll implement something like you had, but be a bit more explicit about what that particular Fake repository is for:

public class FakePonyRepositoryThatOnlyReturnsBrownPonies : IRepository<Pony>
{
    private List<Pony> _verySpecificAndNotReusableListOfOnlyBrownPonies = new List....

    public IEnumerable<Pony> GetAll()
    {
        return _verySpecificAndNotReusableListOfOnlyBrownPonies;
    }
}

public class FakePonyRepositoryThatThrowsExceptionFromGetAll : IRepository<Pony>
{
    public IEnumerable<Pony> GetAll()
    {
        throw new OmgNoPoniesException();
    }
}

You mentioned CSV files as well - this could be viable (have used XML in the past), but I'd argue that holding fake data in a CSV or XML is just a worse version of keeping data in a localised DB using SQL CE or some equivalent. However, both of those are less maintainable and, crucially, in terms of unit tests, slower than using in-memory fake objects. I personally wouldn't use a file-based approach anymore unless I was specifically testing serialization or IO or something.

Hope there's something useful among all that lot...

like image 80
Greg Smith Avatar answered Mar 06 '23 11:03

Greg Smith