iTranslated by AI
Getting Started with Moq for Unit Testing in .NET
What is Moq?
It is a package for mocking (stubbing) external modules used in .NET unit testing.
For example, if the class being tested communicates externally via HTTP or a serial port, it is not easy to set up unit tests as-is (e.g., you would need to set up a test server).
Moq is used to replace those parts that perform HTTP or serial port communication with dummy test modules.
Environment
Windows 10 Pro 2014
Visual Studio 2019 Version 16.6.3
Language: C# (.NET Framework 4.7.2)
Unit Test Project (.NET Framework)
Moq 4.14.5 (Installed via NuGet)
Class to be Tested
Let the class to be tested be one that communicates internally via a serial port.
We will name it Communication.
Common Issues
As a common example:
class Communication
{
SerialPort port = new SerialPort();
...
}
This is the case where the class being tested directly creates an instance of SerialPort internally.
In this state, it cannot be supported by Moq.
Supporting Unit Tests
Regarding the implementation of SerialPort, we will pass it from the outside via an interface, and provide a dummy implementation during unit testing instead.
- Interface definition to be passed from the outside
public interface ISerialPortEx
{
void Open();
string PortName { get; set; }
bool IsOpen { get; }
void WriteLine(string text);
string ReadLine();
string ReadTo(string text);
...
}
- Replacement class for SerialPort
class SerialPortEx : ISerialPortEx // Inherits ISerialPortEx
{
SerialPort port = new SerialPort();
public void Open()
{
this.port.Open();
}
...
}
This class also needs testing, but it is not covered in this article.
- Class being tested
The class being tested will look like the following, using a mechanism where it usesSerialPortExindirectly throughISerialPortEx.
public class Communication
{
ISerialPortEx port;
public Communication(ISerialPortEx port)
{
this.port = port;
}
public string PortName
{
get
{
return this.port.PortName;
}
set
{
this.port.PortName = value;
}
}
public void Connect()
{
this.port.Open();
}
public bool IsConnected
{
get
{
return this.port.IsOpen;
}
}
public void SendMessage(string text)
{
this.port.WriteLine("Msg:"+text);
}
public string RecvMessage()
{
return this.port.ReadLine();
}
public string ReadTo(string text)
{
return this.port.ReadTo(text);
}
}
Implementation example:
SerialPortEx port = new SerialPortEx();
Communication com = new Communication(port);
com.PortName = "COM1";
com.Connect();
if(com.IsConnected)
{
com.SendMessage("test message");
string response = com.RecvMessage();
}
...
Basic Unit Test Implementation
Method with No Arguments and Void Return Type
This is a test that calls the void Connect() method of the Communication class. Inside Connect(), Open() of ISerialPortEx is called.
Since it has no arguments and no return value, this is a case that cannot be inspected using standard unit test syntax like Assert.AreEqual().
[TestMethod]
public void TestConnect()
{
// Setup
var mock = new Mock<ISerialPortEx>(); // Create Mock
ISerialPortEx port = mock.Object; // Get dummy class inheriting ISerialPortEx
// Code being tested
Communication com = new Communication(port);
com.Connect();
// Mock verification
mock.Verify(o => o.Open(), Times.Once);
}
Finally, it verifies that the method is working correctly by checking that the Open() method of ISerialPortEx was called exactly once as a result of the mock call.
Testing Set Properties
This is a test for setting a value to the PortName property of the Communication class.
[TestMethod]
public void TestPortName()
{
// Setup
var mock = new Mock<ISerialPortEx>(); // Create Mock
ISerialPortEx port = mock.Object; // Get dummy class inheriting ISerialPortEx
// Code being tested
Communication com = new Communication(port);
port.PortName = "COM1";
// Mock verification
mock.VerifySet(o => o.PortName = "COM1", Times.Once);
}
In the mock verification, it checks that the process of assigning "COM1" to the PortName property of ISerialPortEx was called once.
Testing Get Properties
This is a test for reading a value from the IsConnected property of the Communication class. Inside IsConnected, IsOpen of ISerialPortEx is called.
[TestMethod]
public void TestIsConnected()
{
// Setup
var mock = new Mock<ISerialPortEx>(); // Create Mock
mock.SetupGet(o => o.IsOpen).Returns(true); // Set to return true as the value for IsOpen
ISerialPortEx port = mock.Object;
Communication com = new Communication(port);
com.Connect();
if (com.IsConnected) {
// ...
}
// Code being tested
Assert.IsTrue(com.IsConnected);
// Mock verification
mock.VerifyGet(o => o.IsOpen, Times.Once);
}
The value that IsOpen should return is configured before executing the code being tested. In this test method, by providing the value that should make the target code succeed, it verifies that it operates as expected. The mock verification checks whether IsOpen was called once.
Method with Arguments and Void Return Type
This is a test that calls the void SendMessage(string text) method of the Communication class. Inside SendMessage(), WriteLine() of ISerialPortEx is called.
[TestMethod]
public void TestSendMessage()
{
// Setup
var mock = new Mock<ISerialPortEx>(); // Create Mock class
mock.SetupGet(o => o.IsOpen).Returns(true); // Set to return true as the value for IsOpen
ISerialPortEx port = mock.Object;
Communication com = new Communication(port);
com.PortName = "COM1";
com.Connect();
if(com.IsConnected)
{
// Code being tested
com.SendMessage("test message");
}
// Mock verification
mock.Verify(o => o.WriteLine("Msg:test message"), Times.Once);
}
In the mock verification, it checks that the WriteLine() method of ISerialPortEx was called once with "Msg:test message" as an argument.
Method with No Arguments and a Return Value
This is a test that calls the string RecvMessage() method of the Communication class. Inside RecvMessage(), ReadLine() of ISerialPortEx is called.
[TestMethod]
public void TestRecvMessage()
{
// Setup
var mock = new Mock<ISerialPortEx>(); // Create Mock class
mock.SetupGet(o => o.IsOpen).Returns(true); // Set to return true as the value for IsOpen
mock.Setup(o => o.ReadLine()).Returns("response message"); // Return value for ReadLine()
ISerialPortEx port = mock.Object;
Communication com = new Communication(port);
com.PortName = "COM1";
com.Connect();
if (com.IsConnected)
{
// Code being tested
Assert.AreEqual("response message", com.RecvMessage());
}
// Test result verification
mock.Verify(o => o.ReadLine(), Times.Once);
}
As setup, the string to be returned by ReadLine() is configured. The mock verification checks that ReadLine() was called once.
Advanced Unit Test Implementation
Cases where an exception occurs
For example, a case where RecvMessage() has no response and a TimeoutException occurs.
[TestMethod]
public void TestRecvMessageTimeout()
{
// Setup
var mock = new Mock<ISerialPortEx>(); // Create Mock class
mock.Setup(o => o.ReadLine()).Throws<TimeoutException>(); // Throw an exception when called
ISerialPortEx port = mock.Object;
Communication com = new Communication(port);
// Code being tested
Assert.ThrowsException<TimeoutException>(() => com.RecvMessage());
// Mock verification
mock.Verify(o => o.ReadLine(), Times.Once);
}
As setup, we configure ISerialPortEx.ReadLine() to throw a TimeoutException when called. We check that the exception is thrown for the RecvMessage() call as a standard unit test evaluation. Finally, in the mock verification, we check that ReadLine() was called once.
Changing property values for each call
This is a case where the value of the IsConnected property in the Communication class changes with each read.
[TestMethod]
public void TestIsConnected2()
{
// Setup
var mock = new Mock<ISerialPortEx>(); // Create Mock class
mock.SetupSequence(o => o.IsOpen)
.Returns(true)
.Returns(false);
ISerialPortEx port = mock.Object;
Communication com = new Communication(port);
com.PortName = "COM1";
com.Connect();
// Code being tested
Assert.IsTrue(com.IsConnected);
Assert.IsFalse(com.IsConnected);
// Mock verification
mock.VerifyGet(o => o.IsOpen, Times.Exactly(2));
}
As setup, we configure IsOpen to return true on the first call and false on the second.
The test code also checks that IsConnected similarly returns true the first time and false the second time. In the mock verification, we check that IsOpen was called exactly twice.
Changing method return values for each call
This is similar to the property case.
[TestMethod]
public void TestRecvMessage2()
{
// Setup
var mock = new Mock<ISerialPortEx>(); // Create Mock class
mock.SetupGet(o => o.IsOpen).Returns(true); // Set to return true as the value for IsOpen
mock.SetupSequence(o => o.ReadLine())
.Returns("response message")
.Returns("response message2");
ISerialPortEx port = mock.Object;
Communication com = new Communication(port);
com.PortName = "COM1";
com.Connect();
if (com.IsConnected)
{
// Code being tested
Assert.AreEqual("response message", com.RecvMessage());
Assert.AreEqual("response message2", com.RecvMessage());
}
// Test result verification
mock.Verify(o => o.ReadLine(), Times.Exactly(2));
}
Similarly for methods, we use SetupSequence() to set the return values for two calls to ReadLine().
Changing method arguments for each call
This is a case where the SendMessage() method of the Communication class is called twice with different values.
[TestMethod]
public void TestSendMessage2()
{
// Setup
var mock = new Mock<ISerialPortEx>(); // Create Mock class
mock.SetupGet(o => o.IsOpen).Returns(true); // Set to return true as the value for IsOpen
ISerialPortEx port = mock.Object;
Communication com = new Communication(port);
com.PortName = "COM1";
com.Connect();
if (com.IsConnected)
{
// Code being tested
com.SendMessage("test");
com.SendMessage("test2");
}
// Mock verification
mock.Verify(o => o.WriteLine("Msg:test"), Times.Once);
mock.Verify(o => o.WriteLine("Msg:test2"), Times.Once);
}
In the final mock verification, we check that the method is called once for each argument value.
Note that if you write the final mock verification as follows, it will check that WriteLine() was called twice with either of the two strings as an argument:
mock.Verify(o => o.WriteLine(It.IsIn<string>("Msg:test", "Msg:test2")),
Times.Exactly(2));
Case where an exception occurs on the second method call
This is a case where the Connect() method of the Communication class succeeds the first time but throws an exception the second time.
[TestMethod]
public void TestConnect2()
{
// Setup
var mock = new Mock<ISerialPortEx>(); // Create Mock class
mock.SetupSequence(o => o.Open())
.Pass() // Do nothing on the 1st call
.Throws<InvalidOperationException>(); // Throw exception on the 2nd call
ISerialPortEx port = mock.Object;
Communication com = new Communication(port);
// Code being tested
com.Connect();
Assert.ThrowsException<InvalidOperationException>(() => com.Connect());
// Mock verification
mock.Verify(o => o.Open(), Times.Exactly(2));
}
In the SetupSequence() of the setup, we configure Open() to do nothing (no arguments, no return value) on the first call and throw an exception on the second call.
The test code also checks that an exception is raised on the second call to Connect(). In the mock verification, we check that Open() of ISerialPortEx was called twice.
Case where the method return value changes based on the argument
This is a case where the return value of the ReadBlock() method of the Communication class changes depending on the argument value. Inside ReadBlock(), string ReadTo(string text) of ISerialPortEx is called.
[TestMethod]
public void TestReadTo()
{
// Setup
var mock = new Mock<ISerialPortEx>(); // Create Mock class
mock.SetupGet(o => o.IsOpen).Returns(true); // Set to return true as the value for IsOpen
mock.Setup(o => o.ReadTo(" ")).Returns("response");
mock.Setup(o => o.ReadTo("\n")).Returns("response message");
ISerialPortEx port = mock.Object;
Communication com = new Communication(port);
com.PortName = "COM1";
com.Connect();
if (com.IsConnected)
{
// Code being tested
Assert.AreEqual("response", com.ReadBlock(" "));
Assert.AreEqual("response message", com.ReadBlock("\n"));
}
// Mock verification
mock.Verify(o => o.ReadTo(" "), Times.Once);
mock.Verify(o => o.ReadTo("\n"), Times.Once);
}
As setup, we configure the return values for each argument of ReadTo(). We then check that behavior in the code being tested. In the mock verification, we check that it was called once for each argument.
Summary
- External modules called by the class being tested should be interfaced and passed in.
- Create a mock as setup and configure the values that methods and properties should return.
- Use standard test syntax like
Assert.AreEqual()for the code being tested. - Finally, verify the values passed as arguments and the number of times the mock was called.
Discussion