Reputation: 31
I've created a shopping basket and I need to do unit tests for it. They were going fine until I came across a small problem, I'd done the unit tests for adding something to the basket etc fine but there's an option to save everything in lst_Results to a folder of your choice in a text file. The code for my save button is:
private void btn_Save_Click(object sender, EventArgs e)
{
var FileSave = new SaveFileDialog();
FileSave.Filter = "Text (*.txt)|*.txt";
if (FileSave.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (var streamwriter = new StreamWriter(FileSave.FileName, false))
foreach (var item in lst_Results.Items)
streamwriter.Write(item.ToString() + Environment.NewLine);
MessageBox.Show("Success");
}
}
then for the actual unit test itself this is what I have so far but I'm not sure what the targets and asserts should be
[TestMethod()]
public void SaveItems()
{
Basket.Basket target = new Basket.Basket();
string itemname = "Orange";
int quantity = 5;
decimal price = 5;
target.AddProduct(itemname, quantity, price);
string itemname2 = "Banana";
int quantity2 = 5;
decimal price2 = 1;
target.AddProduct(itemname2, quantity2, price2);
target.???();
Assert.???);
}
Upvotes: 1
Views: 350
Reputation: 2786
(Re) Consider what you want to test. Do you want to test the UI or test the writing of the data to the stream?
Testing the button is part of integration testing and is not trivial in unittests.
I would suggest to extract the writing the stream into it's own method first
internal static void WriteToStream(IEnumerable<Basket.Basket> items,string filename){
using (var streamwriter = new StreamWriter(filename, false))
foreach (var item in items){
streamwriter.Write(item.ToString() + Environment.NewLine);
}
}
}
private void btn_Save_Click(object sender, EventArgs e)
{
var FileSave = new SaveFileDialog();
FileSave.Filter = "Text (*.txt)|*.txt";
if (FileSave.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
WriteToStream(lst_Results.Items, FileSave.FileName);
MessageBox.Show("Success");
}
}
(method marked internal to allow access from a external assembly holding the tests)
Now you have isolated the stream part. This you can test by using a temporary file and assert after writing by reading back that the contents of the file match with what you expect.
This is not optimal yet. You now have dependency on the filesystem, and for small reads/writes this shouln't hamper much. But you can add another indirection to improve testing.
Consider a further rewrite (refactoring actually) of WriteToStream()
internal static void WriteToStream(IEnumerable<Basket.Basket> items, Func<TextWriter> createStream){
using (var streamwriter = createStream())
foreach (var item in items){
streamwriter.Write(item.ToString() + Environment.NewLine);
}
}
}
private void btn_Save_Click(object sender, EventArgs e)
{
var FileSave = new SaveFileDialog();
FileSave.Filter = "Text (*.txt)|*.txt";
if (FileSave.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
WriteToStream(lst_Results.Items,() => new StreamWriter(FileSave.FileName, false));
MessageBox.Show("Success");
}
}
}
Now you can create a faked (mocked) StreamWriter on which you can assert that items are written to a streamwriter: You only need to make sure that data is written to the writer, how and when the writer get's it to disk is not of your concern.
class FakeStreamWriter : TextWriter{ //StreamWriter IS A TextWriter
public bool HasWritten {get; private set;}
public override void Write(string content){
HasWritten = true;
}
//omitted other methods for brevity
}
[Fact]
public void ItemAreWrittenToStream(){
var myFakedStreamWriter = new FakeStreamWriter();
var items = new List<Basket.Basket>{ new Basket.Basket{...}}
FormX.WriteToStream(item, () => myFakedStreamWriter );
Assert.True( myFakedStreamWriter.HasWritten );
}
Upvotes: 2
Reputation: 704
Your button click event handler does more than it should. That makes unit testing difficult. I would proceed as follows:
Extract the saving part from the button click event handler. For example, you would have a method like this:
public void SaveItemsTo(String fileName, IEnumerable items){
using (var streamwriter = new StreamWriter(fileName, false))
foreach (var item in items){
streamwriter.Write(item.ToString() + Environment.NewLine);
}
}
Once that is done you can easily test saving items from a basket. Your unit test is only missing the lines for saving (which you can do now by calling the extracted method) and asserting that the file has been created (optionally you could also check the file content).
YourClass.SaveItemsTo(yourfilename,Basket.Products);
Assert.IsTrue(File.Exists(yourfilename));
Thats obviously just draft to give you the idea. Obviously there are tons of possible refinements. Where you put the method to save items is entirely up to you.
Upvotes: 0
Reputation: 844
The save (and load if you have it) code should probably moved into method on the Basket. The code in your btn_Save_Click that opens the stream and writes out the data would go into the save method of the basket. Then your test code can call target.Save("filename.ext"); Your assert should then test that the file that was created matches what you expect.
If you want to be more sophisticated, and avoid having to do disk I/O, which might be important in some automated testing environments, then you can make your save code accept a stream as the target, then in your test code provide a memory stream instead of a file stream. The test code could then inspect the memory stream and bypass file I/O all together.
When writing tests, my personal opinion is that the hardest thing to get right, is making sure you are testing the right things. For example, You don't really need to test that FileStream and flushing to disk works, because that's not your code. What is your code is the stuff that writes to the stream itself, and that is what you want to test. This is why it would be acceptable for the test to provide a memory stream when testing the save, even though the code in a production setup would never use a memory stream.
Another useful lesson here is that writing the tests can cause you to find what might be an OOP design issue. Usually objects that have good OOP principals are very easy to test, and if they are not that might be a clue that your object design needs work. In this case, moving the stream writing code into its own method.
Upvotes: 0