Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle setting up complex unit test and have them only test the unit

I have a method that takes 5 parameters. This method is used to take a bunch of gathered information and send it to my server.

I am writing a unit test for this method, but I am hitting a bit of a snag. Several of the parameters are Lists<> of classes that take some doing to setup correctly. I have methods that set them up correctly in other units (production code units). But if I call those then I am kind of breaking the whole idea of a unit test (to only hit one "unit").

So.... what do I do? Do I duplicate the code that sets up these objects in my Test Project (in a helper method) or do I start calling production code to setup these objects?

Here is hypothetical example to try and make this clearer:

File: UserDemographics.cs

class UserDemographics
{
     // A bunch of user demographic here
     // and values that get set as a user gets added to a group.
}

File: UserGroups.cs

class UserGroups
{
     // A bunch of variables that change based on 
     //  the demographics of the users put into them.
     public AddUserDemographicsToGroup(UserDemographcis userDemographics)
     {}
}

File: UserSetupEvent.cs

class UserSetupEvent
{
     // An event to record the registering of a user
     // Is highly dependant on UserDemographics and semi dependant on UserGroups
     public SetupUserEvent(List<UserDemographics> userDemographics, 
                           List<UserGroup> userGroups)
     {}
}

file: Communications.cs

class Communications
{
     public SendUserInfoToServer(SendingEvent sendingEvent, 
                                 List<UserDemographics> userDemographics,
                                 List<UserGroup> userGroups, 
                                 List<UserSetupEvent> userSetupEvents)
     {}
}

So the question is: To unit test SendUserInfoToServer should I duplicate SetupUserEvent and AddUserDemographicsToGroup in my test project, or should I just call them to help me setup some "real" parameters?

like image 607
Vaccano Avatar asked Nov 14 '22 07:11

Vaccano


1 Answers

You need test duplicates.

You're correct that unit tests should not call out to other methods, so you need to "fake" the dependencies. This can be done one of two ways:

  1. Manually written test duplicates
  2. Mocking

Test duplicates allow you to isolate your method under test from its dependencies.

I use Moq for mocking. Your unit test should send in "dummy" parameter values, or statically defined values you can use to test control flow:

public class MyTestObject
{
       public List<Thingie> GetTestThingies()
       {
             yield return new Thingie() {id = 1};
             yield return new Thingie() {id = 2};
             yield return new Thingie() {id = 3};
       } 
}

If the method calls out to any other classes/methods, use mocks (aka "fakes"). Mocks are dynamically-generated objects based on virtual methods or interfaces:

Mock<IRepository> repMock = new Mock<IRepository>();
MyPage obj = new MyPage() //let's pretend this is ASP.NET
obj.IRepository = repMock.Object;
repMock.Setup(r => r.FindById(1)).Returns(MyTestObject.GetThingies().First());
var thingie = MyPage.GetThingie(1);

The Mock object above uses the Setup method to return the same result for the call defined in the r => r.FindById(1) lambda. This is called an expecation. This allows you to test only the code in your method, without actually calling out to any dependent classes.

Once you've set up your test this way, you can use Moq's features to confirm that everything happened the way it was supposed to:

//did we get the instance we expected?
Assert.AreEqual(thingie.Id, MyTestObject.GetThingies().First().Id); 
//was a method called?
repMock.Verify(r => r.FindById(1));

The Verify method allows you to test whether a method was called. Together, these facilities allow you focus your unit tests on a single method at a time.

like image 124
Dave Swersky Avatar answered Dec 19 '22 00:12

Dave Swersky