2

I'm trying to write a console application in C# (which I'm VERY new to) using TDD.

I'm trying to iterate through a dictionary containing strings as keys and integers as values, and output them to the console as "key: value". I've tried loads of different things and finally found something which might work:

    public void ShowContents ()
    {
        foreach (KeyValuePair<string, int> item in dictionary) {
            Console.WriteLine ("{0}: {1}", item.Key, item.Value);
        }
    }

The problem I'm having is - I don't know how to test this. My test looks like this at the moment:

[Test ()]
    public void CanShowContentsOfDictionary ()
    {
        dictionary.AddWord ("Hello");
        Assert.AreEqual ("Hello: 1", dictionary.ShowContents ());
    }

And is obviously expecting a return value rather than something being output to console. I read elsewhere on here that there was no point testing for Console.WriteLine as you just assume it works, and so instead you can use return in this method and then write another method that just writes to console (and therefore doesn't need to be tested). The problem with this is that a) I don't know how to write a method that returns all the keys and values; b) I'm not returning one thing, but a number of different strings.

Any advice as to how I go about this?

Blorgbeard
  • 101,031
  • 48
  • 228
  • 272
Emily
  • 29
  • 5
  • 1
    possible duplicate of [Unit testing void methods?](http://stackoverflow.com/questions/246038/unit-testing-void-methods) – Jeroen Vannevel Mar 29 '15 at 22:17
  • What are you trying to test? Are you trying to test whether the `dictionary` contains expected keys and values, or are you trying to test whether the expected output appears on the console? – Shaun Luttin Mar 29 '15 at 22:26
  • Don't write a method called `ShowContents`, write a function which returns the contents as a string (look at `StringBuilder` class). Then you can easily test that function, without involving Console.WriteLine – Blorgbeard Mar 29 '15 at 22:27
  • Thanks @JeroenVannevel having read through that I realise there are definitely some issues here (and that this is TDD badly done). I'm still slightly unsure how to proceed though. – Emily Mar 29 '15 at 22:27
  • possible duplicate http://stackoverflow.com/questions/1286518/unit-test-help-how-do-i-test-for-a-message-output-to-console – Shaun Luttin Mar 29 '15 at 22:31

2 Answers2

3

You could write a function to return the entire string that would have been output by ShowContents; something like:

public string GetContents()
{
    var sb = new StringBuilder();        
    foreach (KeyValuePair<string, int> item in dictionary) {
        sb.AppendLine(string.Format("{0}: {1}", item.Key, item.Value));
    }
    return sb.ToString();
}

Then your ShowContents is just:

Console.Write(GetContents());

And you can test that GetContents returns what you expect, without involving Console.WriteLine.

Blorgbeard
  • 101,031
  • 48
  • 228
  • 272
  • Thank you - I actually arrived at almost exactly this solution after reading your comment above. I'm really pleased that I managed to do it (almost) by myself. I'd upvote you if I could :) – Emily Mar 29 '15 at 22:46
1

Though you already accepted Blorgbeard answer (which is apparently entirely valid in this case), I'd like to add my two cents to your question.

As far as I understand it, you must test the behavior you want to achieve. Effectively the change between your question and the answer makes the test change from "I want to test that this method writes all values to a text reader" to "I want to test that this method returns all values concatenated". You're not testing a writing behavior anymore, rather a formatting one.

In your case this change is not a problem, but it could be problematic. For example imagine that you want to write a big set of data to a stream. You may want to write it piece by piece in order not to load the entirety of the data in memory. If you test for the concatenated string output your test may pass (if you don't use the full data set, which you usually don't in tests) and fail in production because there is too much data.

Anyway, you could test the writing behavior by passing to the method a TextWriter. Then in your test you can pass a custom TextWriter that lets you check written data. In your production code you would pass Console.Out to write to the console.

public void ShowContents (TextWriter writer)
{
    foreach (KeyValuePair<string, int> item in dictionary) {
        writer.WriteLine ("{0}: {1}", item.Key, item.Value);
    }
}

// calling in production code
ShowContents(Console.Out);

You can also use the Console.SetOut method if you want to prepare the Console for your tests instead of passing a TextWriter. You can find more information in this article by Mark Seeman. I don't really like to change the output of the Console because I don't like that the method writes to a static text writer. Nothing tells you that the function writes to the standard output so I prefer to pass the TextWriter as an argument

samy
  • 14,832
  • 2
  • 54
  • 82