2

How to input data to method of a class in MSTest. I have following code but am not able to input in GetData() while doing unit test.

public class MyData
{
    private string _name;

    public void GetData()
    {
        Console.WriteLine("Please Enter your Name(only Alphabet)");
        _name = Console.ReadLine();
        Console.WriteLine(_name);
    }
}

Test Class

[TestClass]
public class UnitTest1
{
    MyData dd = new MyData();

    [TestMethod]
    public void TestMethod1()
    {
        dd.GetData();
    }
} 
Failed Scientist
  • 1,977
  • 3
  • 29
  • 48
map125
  • 83
  • 1
  • 2
  • 8
  • http://stackoverflow.com/questions/11209639/can-i-write-into-console-in-a-unit-test-if-yes-why-the-console-window-is-not-o – Tommy Jun 24 '16 at 02:29
  • 1
    The right way to do this is to wrap the console in a class with an interface and use a fake version for tests. – SLaks Jun 24 '16 at 02:30
  • 1
    Or pull your actual logic away from the console and just test that. – SLaks Jun 24 '16 at 02:30
  • @map125: [In my answer](http://stackoverflow.com/a/38005424/569302) I demonstrated both of SLak's suggestions. – Jesus is Lord Jun 24 '16 at 04:06

2 Answers2

3

What I'd do is abstract away the Console methods via an interface. Your method shouldn't care about how Console works. You might want to change how you write or read a line later. Something like this:

public interface IConsoleMethods
{
    void WriteLine(string message);
    string ReadLine();
}

You could implement it the way you did like so:

public class ConsoleMethods : IConsoleMethods
{
    public void WriteLine(string message)
    {
        Console.WriteLine(message);
    }

    public string ReadLine()
    {
        return Console.ReadLine();
    }
}

You'll have to create a constructor for myData which accepts IConsoleMethods to initialize it. Ideally, you'd want to inject it based on usage.

public class MyData
{
    public MyData(IConsoleMethods consoleMethods) { this.console = consoleMethods; }

    public IConsoleMethods console;
    private string _name;

    public void GetData()
    {
        console.WriteLine("Please Enter your Name(only Alphabet)");
        _name = console.ReadLine();
        console.WriteLine(_name);
    }
}

You could have your original functionality using:

var myData = new MyData(new ConsoleMethods());
myData.GetData();

And, finally, you could test it using a Mock and setting expectations of your new Console class like so (I've used Moq for mocking):

[TestClass]
public class MyDataTests
{
    [TestMethod]
    public void GetDataTest()
    {
        const string expectedDisplayMessage = "Please Enter your Name(only Alphabet)";
        const string readString = "test";

        var consoleMock = new Mock<IConsoleMethods>();
        consoleMock.Setup(c => c.ReadLine()).Returns(readString);

        var dd = new MyData(consoleMock.Object);

        dd.GetData();

        //Check that writeline was called twice
        consoleMock.Verify(c => c.WriteLine(It.IsAny<string>()), Times.Exactly(2));
        //Check that writeline was called with you display message
        consoleMock.Verify(c=>c.WriteLine(expectedDisplayMessage), Times.Once);
        //check that Readline was called once
        consoleMock.Verify(c=>c.ReadLine(),Times.Once);
        //Check that writeline was called with your test string once
        consoleMock.Verify(c=>c.WriteLine(readString), Times.Once);
    }
}
Ash
  • 5,786
  • 5
  • 22
  • 42
1

I used NUnit (instead of MSTest) and Moq (to mock the unit tests).

Explanation is in comments within code.

using System;
using AlternativesCommon;
using NUnit.Framework;
using Moq;

/// <summary>
/// This demostrates how to use all approaches.
/// </summary>
namespace Usage
{
    public class Program
    {
        public static void Main()
        {
            var myData = new Original.MyData();
            myData.GetData();

            var myData1 = new Alternative1.MyData(new StandardConsole());
            myData1.GetData();

            var myData2 = new Alternative2.MyDataController(
                new StandardConsole(), 
                new Alternative2.MyData());
            myData2.GetData();
        }
    }

}

/// <summary>
/// This demonstrates how to test each approach.
/// </summary>
namespace Tests
{ 
    public class Alternative1Tests
    {
        /// <summary>
        /// The original apporach is too tightly coupled to be unit tested.
        /// </summary>
        [Test]
        public void Original_MyDataCannotBeAutomatedTested()
        {
        }

        /// <summary>
        /// The first alternative abstracts the console out.
        /// This is better, but still requires a lot of mocking and syntax.
        /// </summary>
        [Test]
        public void Alternative1_MyDataShouldWork()
        {
            // Arrange
            var mockConsole = new Mock<IConsole>();

            mockConsole.Setup(c => c.WriteLine(
                "Please Enter your Name(only Alphabet)"));
            mockConsole.Setup(c => c.ReadLine()).Returns("John");
            mockConsole.Setup(c => c.WriteLine("John"));

            var myData = new Alternative1.MyData(mockConsole.Object);

            // Act
            myData.GetData();

            // Assert
            mockConsole.VerifyAll();
        }

        /// <summary>
        /// The second alternative abstracts the data model out.
        /// This allows us to unit test just our domain logic.
        /// We can test the controller in a larger boundary or 
        /// integration test if we want (not shown).
        /// </summary>
        [Test]
        public void Alternative2_MyDataShouldWork()
        {
            // Arrange
            var name = "John";
            var myData = new Alternative2.MyData();
            var initialValue = myData.Name;

            // Act
            myData.Name = name;

            // Assert
            Assert.That(initialValue, Is.Null);
            Assert.That(myData.Name, Is.EqualTo(name));
        }

        /// <summary>
        /// This shows how one would unit test the controller.
        /// Lots of mocking (ew!).
        /// </summary>
        [Test]
        public void Alternative2_MyDataControllerShouldWork()
        {
            // Arrange
            var mockConsole = new Mock<IConsole>();

            mockConsole.Setup(c => c.WriteLine(
                "Please Enter your Name(only Alphabet)"));
            mockConsole.Setup(c => c.ReadLine()).Returns("John");
            mockConsole.Setup(c => c.WriteLine("John"));

            string name = null;
            var mockData = new Mock<Alternative2.IMyData>();
            mockData.SetupGet(d => d.Name).Returns(() => name);
            mockData.
                SetupSet(d => d.Name = It.IsAny<string>()).
                Callback((string value) => name = value);

            var controller = new Alternative2.MyDataController(
                mockConsole.Object, 
                mockData.Object);

            // Act
            controller.GetData();

            // Assert
            mockConsole.VerifyAll();
            mockData.VerifyAll();
        }
    }
}

namespace Original
{
    public class MyData
    {
        private string _name;

        public void GetData()
        {
            Console.WriteLine("Please Enter your Name(only Alphabet)");
            _name = Console.ReadLine();
            Console.WriteLine(_name);
        }
    }
}

namespace Alternative1
{
    public class MyData
    {
        private string _name;
        private IConsole _console;

        public MyData(IConsole console)
        {
            this._console = console;
        }

        public void GetData()
        {
            this._console.WriteLine("Please Enter your Name(only Alphabet)");
            this._name = this._console.ReadLine();
            this._console.WriteLine(this._name);
        }
    }
}

namespace Alternative2
{
    public interface IMyData
    {
        string Name
        {
            set;
            get;
        }
    }

    public class MyData : IMyData
    {
        private string _name;

        public string Name
        {
            set
            {
                // Do any validation here.  
                // For example, uncomment out the following 
                // (but don't forget to test!):

                //if (string.IsNullOrEmpty(value))
                //{
                //    throw new Exception(
                //        @"Name cannot be empty or null.");
                //}

                //if (value.Length > 100)
                //{
                //    throw new Exception(
                //        @"Name cannot be longer than 100 characters.");
                //}

                this._name = value;
            }
            get
            {
                return this._name;
            }
        }
    }

    public class MyDataController
    {
        private IConsole _console;
        private IMyData _data;

        public MyDataController(IConsole console, IMyData data)
        {
            this._console = console;
            this._data = data;
        }

        public void GetData()
        {
            this._console.WriteLine("Please Enter your Name(only Alphabet)");
            this._data.Name = this._console.ReadLine();
            this._console.WriteLine(this._data.Name);
        }
    }
}

/// <summary>
/// Defines the console abstraction used by both alternatives.
/// </summary>
namespace AlternativesCommon
{
    public interface IConsole
    {
        string ReadLine();
        void WriteLine(string line);
    }

    public class StandardConsole : IConsole
    {
        public string ReadLine()
        {
            return Console.ReadLine();
        }

        public void WriteLine(string line)
        {
            Console.WriteLine(line);
        }
    }
}
Jesus is Lord
  • 14,971
  • 11
  • 66
  • 97
  • `mockConsole.Setup(c => c.WriteLine(` is not required – Ash Jun 27 '16 at 00:59
  • 1
    With due respect, this answer is extreme overkill. – Ash Jun 27 '16 at 01:12
  • In regard to your first comment, I thought mocking the `WriteLine` calls makes it so that it fails if `WriteLine` is not called (using `Verify`). We want the test to fail if the component does not call `WriteLine`, yes? Therefore, it would need to be mocked. – Jesus is Lord Jun 27 '16 at 03:12
  • @AshwinNair: In regard to your second comment, I agree for OP's question this is overkill. Absolutely. I don't take that disrespectfully at all. That being said I assumed OP provided a non-real-world toy example that was condensed; so my answer is more towards the general case of illustrating how to mock `static` dependencies in alternative 1 and how to isolate business logic from everything else in alternative 2. Very important concepts for complex domains that have changing requirements, although obviously extremely overkill for this also probably contrived problem. – Jesus is Lord Jun 27 '16 at 03:14
  • But you also want the test to fail if the `WriteLine` is called more than once, don't you? I'm not a fan of `VerifyAll()`. It's easy to overlook assumptions that are not explicitly stated. Also, yes, I meant overkill WRT the OP's question. +1 for being thorough though. – Ash Jun 27 '16 at 04:02
  • @AshwinNair: That's a good point about `VerifyAll`. I wasn't doing a super thorough job around mocking (partially because I didn't want that to be the focus of my answer). I see you were more explicit in your answer about mocking. – Jesus is Lord Jun 27 '16 at 13:43