Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing return value of method called from inside another method

I have a method similar to this:

public List<MyClass> DoSomething(string Name, string Address, string Email, ref string ErrorMessage)
{
  //Check for empty string parameters etc now go and get some data   
  List<MyClass> Data = GetData(Name, Address, Email);

  /*************************************************************    
  //How do I unit test that the data variable might be empty???    
  *************************************************************/

  List<MyClass> FormattedData = FormatData(Data);
  return FormattedData;
}

I'm just learning TDD/Unit Testing. My question is, how do I write a test to ensure that if GetData returns an empty list I set ErrorMessage to something and then return a empty list?

like image 647
Jon Avatar asked Feb 22 '23 17:02

Jon


2 Answers

When developing you should have left an "entrance point" for testing like this:

public List<MyClass> DoSomething(string Name, string Address,
              string Email, ref string ErrorMessage, IDataProvider provider)
{
  //Check for empty string parameters etc now go and get some data   
  List<MyClass> Data = provider.GetData(Name, Address, Email);

  List<MyClass> FormattedData = FormatData(Data);
  return FormattedData;
}

And in the Unit Testing you should mock the IDataProvider

It's called Dependency Injection (DI) or Inversion Of Control (IOC)

common mocking library:

  • NMock
  • EasyMock.NET
  • TypeMock Isolator Commercial / Paid
  • Rhino Mocks
  • Moq
  • NSubstitute
  • JustMock Commercial / Paid
  • FakeItEasy
  • Moles

The list was taken from here

Wikipedia articles:

Dependency Injection
Inversion Of Control

NUnit pseudo code:

[Test]
public void When_user_forgot_password_should_save_user()
{
    // Arrange
    var errorMessage = string.Empty;
    var dataProvider = MockRepository.GenerateStub<IDataProvider>();
    dataProvider.Stub(x => x.GetData("x", "y", "z", ref errorMessage )).Return(null);

    // Act
    var result DoSomething("x","y","z", ref errorMessage, dataProvider);

    // Assert

    //...
}
like image 191
gdoron is supporting Monica Avatar answered Apr 09 '23 17:04

gdoron is supporting Monica


Unit testing is not something you add to the middle of existing methods, it is about testing small units of code in isolation from the rest of the system such that you have confidence that the unit is behaving as it should.

So, you should write a second class that's sole responsibility is to test that the class in which DoSomething lives (let's call this class Daddy and the test class DaddyTests) behaves as you expect it to. You can then write a test method that calls DoSomething and ensures that ErrorMessage is set appropriately (also ErrorMessage should be an out param, not ref unless you are also passing a value in).

To facilitate this test you will need to make sure that GetData returns no data. Trivially you can do this by passing in an empty set of data in a fake provider but in more complex scenarios whole classes may have to be swapped out for fake/mock equivalents: the use of interfaces and dependency-injection makes this task very simple. (Typically the provider is set during Daddy's construction, not as a parameter in the call to DoSomething.)

public class Daddy {
    public List<MyClass> DoSomething(string Name, string Address,                  string Email, out string ErrorMessage, IDataProvider provider)
    {
      //Check for empty string parameters etc now go and get some data   
      List<MyClass> Data = provider.GetData(Name, Address, Email);

      if (Data.Count == 0)
      {
          ErrorMessage = "Oh noes";
          return Enumerable.Empty<MyClass>();
      }

      List<MyClass> formattedData = FormatData(Data);
      return formattedData;
    }
}

[TestClass]
public class DaddyTest {
    [TestMethod]
    public void DoSomethingHandlesEmptyDataSet() {
        // set-up
        Daddy daddy = new Daddy();

        // test
        IList<MyClass> result = daddy.DoSomething("blah",
                                                  "101 Dalmation Road",
                                                  "[email protected]",
                                                  out error,
                                                  new FakeProvider(new Enumerable.Empty<AcmeData>())); // a class we've written to act in lieu of the real provider

        // validate
        Assert.NotNull(result); // most testing frameworks provides Assert functionality
        Assert.IsTrue(result.Count == 0);
        Assert.IsFalse(String.IsNullOrEmpty(error));
    }
}

}

like image 22
Paul Ruane Avatar answered Apr 09 '23 16:04

Paul Ruane