5

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?

5
  • using an ErrorMessage flag is a little bit awkward IMO. Commented Jan 26, 2012 at 11:44
  • There could be numerous problems and I want to show the user what the problem is. What alternative would you suggest? Commented Jan 26, 2012 at 11:46
  • If you faced an error-meaning the parameters you've got are wrong. You should trow an exception probably ArgumentException Commented Jan 26, 2012 at 11:48
  • What if the parameters aren't wrong and something else goes wrong? Better to put it in a string than throw exceptions Commented Jan 26, 2012 at 11:57
  • 1. You can throw any kind of exception you want. 2. the Exception class has a message property, puy the error message there. Commented Jan 26, 2012 at 12:31

4 Answers 4

4

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:

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

    //...
}
Sign up to request clarification or add additional context in comments.

5 Comments

Could you expand on your answer and show what the unit test would look like as I can't see what adding a provider does
I added a pseudo code for that. I'm sure it won't compile... it's just an illustration.
To me this seems like a bit of an overkill when you can solve it without mocking
@Mharlin. My answer(Mocking) is suitable for every test case. And it doesn't force you change the method operation or logic(I guess you don't want to use ErrorMessgae string or some other flag to each method...). Mocking gives you the moxt flexiable UnitTest, but that's true that sometimes it's annoying...
You add another a new dependency to the method. That increases the complexity which I think should be avoided when it's not necessary to add it. And if you compare the test method you suggested to the one without a mock you have to type more code and you are also making a test that could break if you change the implementation even though the end result would be the same.
3

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));
    }
}

}

5 Comments

I think I understand but I can't get around how I test if the variable Data is empty to set the ErrorMessage and return
You simply look at Data.Count. (BTW, the general convention is to use lowercase initial letter (camel case) for variable, parameter and field names and uppercase initial letter (Pascal case) for class, event, property and enum member names.). I've updated the example code in the answer.
No, unit-testing is about testing that the results are as expected when given a particular set of inputs. Efficiency and code cleanliness are orthogonal to this but are important, just not normally possible to cover in your unit-tests. Some testing frameworks do let you set expectations upon what methods get called, however, so you could, with effort, ensure that FormatData is not called (as you don't expect it to be when there is no data).
I'm starting to think with your test that I might as well get rid of my other tests that check for blank parameters etc because if they are blank I would hope that an errormessage and a blank list is returned anyway
What's more, you can check the contents of the error message to ensure it is what is expected, e.g. 'email address must be specified' &c. For this class of testing, though, I tend to throw ArgumentNullException, ArgumentException and ArgumentOutOfRangeException as appropriate and have my unit tests expect this exception.
1

IMO, the line

List<MyClass> Data = GetData(Name, Address, Email);

should be outside the class. With a method signature changed to

public List<MyClass> DoSomething(List<MyClass> data, ref string ErrorMessage)

this method becomes much easier to test as you can easily vary the inputs to test all the possible edge cases.

The other alternative is to have the GetData method being exposed by a mock dependency, which you can then setup to return various results. So your class would now look like:

class ThisClass
{
   [Import]
   public IDataService DataService {get; set;}

   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 = IDataService.GetData(Name, Address, Email); // using dependency

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

1 Comment

+1; also if you need to retain the existing signature, you can simply have it delegate to the method with the new, more testable, signature.
1
[TestMethod]
public void MyDoSomethingTest()
{
   string errorMessage = string.Empty;
   var actual = myClass.DoSomething(..., ref errorMessage)
   Assert.AreEqual("MyErrorMessage", errorMessage);
   Assert.AreEqual(0, FormattedData.Count);
}

I'm assuming that if there isn't any datato format the formatter will return an empty list.

Since you want to verify the final outcome of the method I would not try to find out what's returned from the GetData function since it's the actaul return value that you want to verify that it's an empty list and maybe that the FormatData doesn't crash.

If you want to exit the function as quickly as possible you could check if any of the parameters are null och empty and in that case just do

errorMessage = "Empty parameters are not allowed";
return new List<MyClass>();

8 Comments

I see what you're saying but would it not be better to bail out as early as possible negating the need to call FormatData?
You could put in a check if the incomming parameters are null or empty and in that case you return an empty list straight away and set the error message. The result from the function will still be the same. So what you are asserting in the test wouldn't need to change
Added some more detail to my answer
Do you mean add checks on the GetData and GetFormattedData?
@gdoron - I agree that the errorMessage flag is awkward but it is up to the API designer how exceptions will be handled. The unit test should merely test all this and in the end can be used as documentation how the API is intended to be used (note that I do assume that the GetData method is a method on the actual object itself. If this is a <cough> global method, it should indeed be mocked/stubbed/DI/...)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.