Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you create a proper unit test for a method that returns a list?

I have this method:

public DataSourceResult GetProjectBySpec(int projectId, int seasonId, int episodeId)
        {
            using (var rep = RepositoryHelper.GetTake2Repository<ITake2RepositoryBase>())
            {
                var spec = new ProjectCrewsByProjectSpec(projectId, seasonId, episodeId);

                var personList = rep.GetList<ProjectDGACrew>(spec).Select(p => new
                {
                    //big query...
                    .ToDataSourceResult();

                return personList;
            }
        }

I need to create a unit test for this.

My first question is:

  1. What am I testing for? Am I ONLY testing to see if the method returns a list?

  2. If so, how would I go about testing it?

This is what I have so far:

    [TestClass]
    public class CrewControllerTest
    {
        [TestMethod]
        public void GetProjectCrewsBySpecTest()
        {
            // arrange
            int projectId = 1;
            int seasonId = 2;
            int episodeId = 3;

            // act
            var crewController = new CrewController();
            DataSourceResult dsr = crewController.GetProjectCrewsBySpec(1, 2, 3);

            // assert
            // what or how do I assert here? Am I just checking whether "dsr" is a list? How do I do that?
        }
    }
like image 392
JJ. Avatar asked Jul 10 '13 20:07

JJ.


Video Answer


2 Answers

I'm no expert either,and I've only been doing TDD for a small while so take what I write in this rambling answer with a spoonful of salt :) I am sure someone else can point out if I've made any really bad mistakes or pointed you in the wrong direction...

I'm not sure your test is really a Unit Test because it's exercising multiple dependencies. Suppose for a moment that you run this test and get an exception thrown out of the method. Did this exception come from

  • RepositoryHelper.GetTake2Repository()) throwing? (Depencency issue)
  • ProjectCrewsByProjectSpec constructor throwing? (Dependency issue)
  • rep.GetList(spec) throwing? the kendo (it is kendo,right?) (Dependency issue)
  • ToDataSourceResult() throwing? (Behavioural issue)

Unit testing is all about testing things in complete isolation from their dependencies, so at the moment I'd say it's more like an integration test whereby you don't really care how the systems interact, you just want to make sure that for a given projectID, seasonId and episodeId you get back the expected results - in this case what are really testing is the rep.GetList() method in conjunction with the .ToDataSourceResult extension.

Now integration tests are very very useful and 100% required as part of a test driven methodology,and if that's what you really want to do, then you're doing it about right.(I put this in and expect that back; did I get that back?)

But if you want to Unit Test this code (specifically, if you want to unit test your classes' GetProjectBySpec method) you will have to do as @jimmy_keen mentioned and refactor it so that you can test the behaviour of GetProjectBySpec. e.g. here is a specified behaviour I just invented, of course yours might be different:

  • If input is bad, throw ArgumentException
  • Creates a new ProjectCrewsByProjectSpec object
  • Calls rep.GetList and passes spec to it
  • Returns a non-null DataSourceResult

The first thing you need to do in order to be able to test that GetProjectBySpec does all of the things in the above list is to refactor it so that it doesn't create its own dependencies - instead, you give it the dependencies it needs, via Dependency Injection.

DI really works best when you are injecting by Interface, so in whatever class provides this method, your constructor for that class should take an instance of for example IRepositoryHelper and store it in a private readonly member. It should also take an instance of IProjectCrewsByProjectSpecFactory which you'd use to create your spec. Now since you want to test what GetProjectBySpec actually does with these dependencies then you'll be using a mocking framework such as Moq which I won't go into here except in the example below.

If neither of these classes currently implement such an interface, then just use Visual Studio to extract the interface definition for you based on the class definition. If they are 3rd party classes over which you have no control, this might be tricky.

But let's assume for a minute that you can define interfaces thus: (bear with me on the generic <> bits which I'm never 100% on, I am sure someone smarter than me can tell you where all the "T"'s should go...) Code below is not tested nor checked for typos!

public interface IRepositoryHelper<ProjectDGACrew>
{
  IList<ProjectDGACrew> GetList(IProjectCrewsByProjectSpecFactory spec);
}

public interface IProjectCrewsByProjectSpecFactory 
{
 ProjectDGACrew Create(int projectId, int seasonId, int episodeId);
}

Your code would then end up looking something like this:

//somewhere in your class definition
private readonly IRepositoryHelper<T> repo;
private readonly IProjectCrewsByProjectSpecFactory pfactory;
//constructor
public MyClass(IRepositoryHelper<ProjectDGACrew> repo, IProjectCrewsByProjectSpecFactory pfactory)
{
this.repo = repo;
this.pfactory=pfactory;
}
//method to be tested
public DataSourceResult GetProjectBySpec(int projectId, int seasonId, int episodeId)
{
            var spec = pfactory.Create(projectId, seasonId, episodeId);
            var personList = repo.GetList(spec).Select(p => new
                {//big query...}).ToDataSourceResult();
                return personList;
}

now you have 4 test methods to write:

[TestMethod]
[ExepctedException(typeof(ArgumentException)]
public void SUT_WhenInputIsBad_ThrowsArgumentException()
{
    var sut = new MyClass(null,null); //don't care about our dependencies for this check
    sut.GetProjectBySpec(0,0,0); //or whatever is invalid input for you.
    //don't care about the return, only that the method throws.
}



[TestMethod]
public void SUT_WhenInputIsGood_CreatesProjectCrewsByProjectSpec()
{
  //create dependencies using Moq framework.
  var pf= new Mock<IProjectCrewsByProjectSpecFactory>();
  var repo = new Mock<IRepository<ProjectDgaCrew>>();
  //setup such that a call to pfactory.Create in the tested method will return nothing
  //because we actually don't care about the result - only that the Create method is called.
  pf.Setup(p=>p.Create(1,2,3)).Returns<ProjectDgaCrew>(new ProjectDgaCrew());
  //setup the repo such that any call to GetList with any ProjectDgaCrew object returns an empty list
//again we do not care about the result. 
//This mock dependency is only being used here
//to stop an exception being thrown from the test method
//you might want to refactor your behaviours
//to specify an early exit from the function when the factory returns a null object for example.
   repo.Setup(r=>r.GetList(It.IsAny<ProjectDgaCrew>()).Returns<IList<ProjectDGACrew>>(new List<ProjectDgaCrew>());
  //create our System under test, inject our mock objects:
   var sut = new MyClass(repo,pf.Object);
   //call the method:
   sut.GetProjectBySpec(1,2,3);
   //and verify that it did indeed call the factory.Create method.
    pf.Verify(p=>p.Create(1,2,3),"pf.Create was not called with 1,2,3");              
}
        public void SUT_WhenInputIsGood_CallsRepoGetList(){} //you get the idea
        public void SUT_WhenInputIsGood_ReturnsNonNullDataSourceResult(){}//and so on.

Hope that gives you some help...and of course you can refactor your test classes to avoid a lot of the mocking setup and have it all in a single place to keep the lines of code to a minimum.

like image 130
Stephen Byrne Avatar answered Oct 26 '22 05:10

Stephen Byrne


Unit tests usually (should) test contract as understood by class client. When client of your code calls .GetProjectBySpec(1, 2, 3), what is he expecting to happen? Unit test should answer this question:

When there's 5 projects in repository (A, B, C, D, E) and I call GetProjectBySpec with parameters 1, 2, 3, I should get projects B and C

In your case it might depend on what's being done in //big query... part. If it's filtering/transformation of the results returned from repository, this is what you should test for.

Note that you'll probably need to change few things in order to make this test isolated (from repository/database):

  • RepositoryHelper.GetTake2Repository should be wrapped in interface, injected as dependency and mocked later in unit test
  • if new ProjectCrewsByProjectSpec creates complex object, you might want to use factory instead

When you'll have repository mocked, you simply instruct your mock to return some upfront known list of items when it's called with matching spec parameter. Then your unit test can verify whether data returned from GetProjectBySpec meets your expectations.

like image 33
k.m Avatar answered Oct 26 '22 06:10

k.m