📜 ⬆️ ⬇️

Moq usage examples

Moq is a simple and lightweight Isolation Framework, which is built on the basis of anonymous methods and expression trees. To create mocks, it uses code generation, so it allows you to "mock up" interfaces, virtual methods (and even protected methods), and does not allow you to "mock" non-virtual and static methods.

NOTE
There are only two frameworks on the market that allow you to "mock" anything. These are TypeMockIsolator and Microsoft Fakes, available in Visual Studio 2012 (formerly known as Microsoft Moles ). These frameworks, unlike Moq, use not code generation, but the CLR Profiling API, which allows you to hook into almost any method and create mocks / stubs even for static, non-virtual or closed methods.

In Moq, there is no separation between “stubs” (stubs) and “mocks” (mocks) or, more formally, there is no separation between state verification and behavior verification. And although in most cases the differences between stubs and mocks are not so important, and sometimes the same stub performs both roles, we will consider examples from simple to complex, therefore we first consider examples of checking the state, and only then proceed to check the behavior .

State verification


As an example, we will consider the unit test suite for the following interface:
')
public interface ILoggerDependency { string GetCurrentDirectory(); string GetDirectoryByLoggerName(string loggerName); string DefaultLogger { get; } } 


1. GetCurrentDirectory method stub :

 // Mock.Of    (-),   -. //   ,    GetCurrentDirectory() //   "D:\\Temp" ILoggerDependency loggerDependency = Mock.Of<ILoggerDependency>(d => d.GetCurrentDirectory() == "D:\\Temp"); var currentDirectory = loggerDependency.GetCurrentDirectory(); Assert.That(currentDirectory, Is.EqualTo("D:\\Temp")); 


2. The stub of the GetDirectoryByLoggerName method, which always returns the same result:

 //     GetDirectoryByLoggerName  "C:\\Foo". ILoggerDependency loggerDependency = Mock.Of<ILoggerDependency>( ld => ld.GetDirectoryByLoggerName(It.IsAny<string>()) == "C:\\Foo"); string directory = loggerDependency.GetDirectoryByLoggerName("anything"); Assert.That(directory, Is.EqualTo("C:\\Foo")); 


3. The stub method GetDirrectoryByLoggerName , returning the result depending on the argument:

 //    ,    //  GetDirrectoryByLoggerName    . //    : // public string GetDirectoryByLoggername(string s) { return "C:\\" + s; } Mock<ILoggerDependency> stub = new Mock<ILoggerDependency>(); stub.Setup(ld => ld.GetDirectoryByLoggerName(It.IsAny<string>())) .Returns<string>(name => "C:\\" + name); string loggerName = "SomeLogger"; ILoggerDependency logger = stub.Object; string directory = logger.GetDirectoryByLoggerName(loggerName); Assert.That(directory, Is.EqualTo("C:\\" + loggerName)); 


4. DefaultLogger property stubs :

 //  DefaultLogger       ILoggerDependency logger = Mock.Of<ILoggerDependency>( d => d.DefaultLogger == "DefaultLogger"); string defaultLogger = logger.DefaultLogger; Assert.That(defaultLogger, Is.EqualTo("DefaultLogger")); 


5. Setting the behavior of several methods with one expression using the “moq functional specification” (appeared in Moq v4):

 //        «» ILoggerDependency logger = Mock.Of<ILoggerDependency>( d => d.GetCurrentDirectory() == "D:\\Temp" && d.DefaultLogger == "DefaultLogger" && d.GetDirectoryByLoggerName(It.IsAny<string>()) == "C:\\Temp"); Assert.That(logger.GetCurrentDirectory(), Is.EqualTo("D:\\Temp")); Assert.That(logger.DefaultLogger, Is.EqualTo("DefaultLogger")); Assert.That(logger.GetDirectoryByLoggerName("CustomLogger"), Is.EqualTo("C:\\Temp")); 


6. Setting the behavior of several methods by calling the Setup method (“old” v3 syntax):

 var stub = new Mock<ILoggerDependency>(); stub.Setup(ld => ld.GetCurrentDirectory()).Returns("D:\\Temp"); stub.Setup(ld => ld.GetDirectoryByLoggerName(It.IsAny<string>())).Returns("C:\\Temp"); stub.SetupGet(ld => ld.DefaultLogger).Returns("DefaultLogger"); ILoggerDependency logger = stub.Object; Assert.That(logger.GetCurrentDirectory(), Is.EqualTo("D:\\Temp")); Assert.That(logger.DefaultLogger, Is.EqualTo("DefaultLogger")); Assert.That(logger.GetDirectoryByLoggerName("CustomLogger"), Is.EqualTo("C:\\Temp")); 


NOTE
As I already mentioned, in Moq there is no separation between mocks and stubs, however, it will be much easier for us to distinguish between two stub initialization syntax. So, the “moq functional specification” syntax can only be used for state testing (i.e. for stubs) and cannot be used to define behavior. Initialization of the stubs using the Setup method may be, firstly, more verbose, and secondly, when using it, it is not entirely clear whether we are going to check the behavior or state.

Behavior verification


The following class and interface will be used to test the behavior:

 public interface ILogWriter { string GetLogger(); void SetLogger(string logger); void Write(string message); } public class Logger { private readonly ILogWriter _logWriter; public Logger(ILogWriter logWriter) { _logWriter = logWriter; } public void WriteLine(string message) { _logWriter.Write(message); } } 


1. Verification of the call to the ILogWriter .Write method by an object of the Logger class (with any argument):

 var mock = new Mock<ILogWriter>(); var logger = new Logger(mock.Object); logger.WriteLine("Hello, logger!"); // ,    Write      mock.Verify(lw => lw.Write(It.IsAny<string>())); 


2. Verification of a call to the ILogWriter .Write method with the given arguments:

 mock.Verify(lw => lw.Write("Hello, logger!")); 


3. Verify that the ILogWriter .Write method was called exactly once (no more, no less):

 mock.Verify(lw => lw.Write(It.IsAny<string>()), Times.Once()); 


NOTE
There are many options for checking how many times a dependency is caused. For this, there are various methods of the Times class: AtLeast (int), AtMost (int), Exactly, Between, and others.

4. Verify behavior using the Verify method (can be handy when you need to test several assumptions):

 var mock = new Mock<ILogWriter>(); mock.Setup(lw => lw.Write(It.IsAny<string>())); var logger = new Logger(mock.Object); logger.WriteLine("Hello, logger!"); //     Verify   . //  ,      //   mock.Setup mock.Verify(); 


5. Verify multiple calls using the Verify () method.

In some cases, it is inconvenient to use several Verify methods to test multiple calls. Instead, you can create a mock object and set the expected behavior using the Setup methods and check all these assumptions by calling the same Verify () method. Such a technique can be convenient for reusing mock objects created in the Setup Test method.

 var mock = new Mock<ILogWriter>(); mock.Setup(lw => lw.Write(It.IsAny<string>())); mock.Setup(lw => lw.SetLogger(It.IsAny<string>())); var logger = new Logger(mock.Object); logger.WriteLine("Hello, logger!"); mock.Verify(); 


Retreat from the topic. Strict vs Loose models



Moq supports two behavioral testing models: strict (strict) and free (loose). By default, a free model of checks is used, which is that the class under test (Class Under Test, CUT), during the execution of an action (in the Act section) can call any methods of our dependencies and we are not obliged to specify them all.

So, in the previous example, the logger .WriteLine method calls two ILogWriter interface methods : the Write method and the SetLogger method . When using MockBehavior .Strict, the Verify method fails unless we explicitly indicate which exact dependency methods will be called:

 var mock = new Mock<ILogWriter>(MockBehavior.Strict); //      ,  //  mock.Verify()    mock.Setup(lw => lw.Write(It.IsAny<string>())); mock.Setup(lw => lw.SetLogger(It.IsAny<string>())); var logger = new Logger(mock.Object); logger.WriteLine("Hello, logger!"); mock.Verify(); 


Using the MockRepository



The MockRepository class provides another syntax for creating stubs and, most importantly, allows you to store several mock objects and check more complex behavior by calling a single method.

1. Use MockRepository .Of to create stubs.
This syntax is similar to using Mock .Of , however, it allows you to set the behavior of different methods not through the && operator, but by using several Where methods:

 var repository = new MockRepository(MockBehavior.Default); ILoggerDependency logger = repository.Of<ILoggerDependency>() .Where(ld => ld.DefaultLogger == "DefaultLogger") .Where(ld => ld.GetCurrentDirectory() == "D:\\Temp") .Where(ld => ld.GetDirectoryByLoggerName(It.IsAny<string>()) == "C:\\Temp") .First(); Assert.That(logger.GetCurrentDirectory(), Is.EqualTo("D:\\Temp")); Assert.That(logger.DefaultLogger, Is.EqualTo("DefaultLogger")); Assert.That(logger.GetDirectoryByLoggerName("CustomLogger"), Is.EqualTo("C:\\Temp")); 


2. Use MockRepository to set the behavior of several mock objects.
Suppose we have a more complex class, SmartLogger , which requires two dependencies: ILogWriter and ILogMailer . Our test class, when calling its Write method, should call the methods of two dependencies:

 var repo = new MockRepository(MockBehavior.Default); var logWriterMock = repo.Create<ILogWriter>(); logWriterMock.Setup(lw => lw.Write(It.IsAny<string>())); var logMailerMock = repo.Create<ILogMailer>(); logMailerMock.Setup(lm => lm.Send(It.IsAny<MailMessage>())); var smartLogger = new SmartLogger(logWriterMock.Object, logMailerMock.Object); smartLogger.WriteLine("Hello, Logger"); repo.Verify(); 


Other technology



In some cases, it is useful to get the mock object itself via the interface (get Mock <ISomething > through the ISomething interface). For example, the functional syntax for stub initialization does not return a mock object, but the required interface immediately. This is convenient for testing a couple of simple methods, but it is inconvenient if you also need to check the behavior, or set a method that returns different results for different parameters. So sometimes it is convenient to use LINQ-based syntax for one part of the methods and use the Setup methods for another:

 ILoggerDependency logger = Mock.Of<ILoggerDependency>( ld => ld.GetCurrentDirectory() == "D:\\Temp" && ld.DefaultLogger == "DefaultLogger"); //      GetDirectoryByLoggerName //    ,     Mock.Get(logger) .Setup(ld => ld.GetDirectoryByLoggerName(It.IsAny<string>())) .Returns<string>(loggerName => "C:\\" + loggerName); Assert.That(logger.GetCurrentDirectory(), Is.EqualTo("D:\\Temp")); Assert.That(logger.DefaultLogger, Is.EqualTo("DefaultLogger")); Assert.That(logger.GetDirectoryByLoggerName("Foo"), Is.EqualTo("C:\\Foo")); Assert.That(logger.GetDirectoryByLoggerName("Boo"), Is.EqualTo("C:\\Boo")); 


In addition, Moq allows you to check the behavior of protected methods, test events and contains some other features.

Additional links



Github examples
Moki and stubs
Microsoft Moles

Source: https://habr.com/ru/post/150859/


All Articles