📜 ⬆️ ⬇️

We work with TypeMock Isolator

For a dotnet developer who plans unit testing, the question rarely arises as to what is meant by this notorious “unit”: in the overwhelming number of cases, a unit is a class, and thus any test that uses two or more unit classes is not - this is already an integration test. Here, of course, we are talking about our classes, since binding to the framework or third-party libraries is quite a normal thing, and does not need testing (although how to say ...).

So, a problem has arisen: how to test a class, but instead of using other classes they use to put some objects that themselves do nothing (for example, do not write data to the database), but at the same time return the expected values ​​or throw out exceptions? The solution to this problem is given by the mock frameworks that help us create these cunning fake objects or “mocks” (from English mock - a copy, imitation). Let's use the TypeMock library and see how these “mocks” work in action.

What do we need to use mocks? Three things - Visual Studio, TypeMock, and a suitable framework for unit testing (I'll use MbUnit). Well, the desire to study it all.

The first steps


First, a small script. Suppose that I have a system of accounting for workings which considers how much I owe to an employee. It looks like this:
')
 public class Worker
 {
   private List <int> workHours {get;  set;  }
   public int GetTotalHoursWorked () {return workHours.Sum ();  }
 }
 public class Payroll
 {
   public int CalculatePay (Worker worker)
   {
     // pay everyone 10 dollars an hour (I am evil)
     return worker.GetTotalHoursWorked () * 10;
   }
 }

We have a class Worker (worker), which produces a certain amount of work, and this clock is added to an array. The payment system - Payroll - receives the amount of these hours, multiplies them by the hourly rate, and makes payments. Now imagine that we want to test the Payroll class, but in order for it to be a real unit test, we need to isolate the dependent Person class so that, for example, the GetTotalHoursWorked() function is not called at all . How to do it? Very simple: First, we create a Payroll as usual, but instead of Person we create a mock object:

 Worker w = Isolate.Fake.Instance <Worker> ();
 Payroll p = new Payroll ();

Now our employee is like an employee, but with some configurable filling [ 1 ]. Now we want the count to pass, but the real Worker will not be affected. To do this, you need to change the GetTotalHoursWorked() call. Here is how it is done:

 Isolate.WhenCalled (() => w.GetTotalHoursWorked ()). WillReturn (40);

Everything is simple - instead of subsequent calls to Person.GetTotalHoursWorked() number 40 will be Person.GetTotalHoursWorked() returned. If you do not believe it, set a breakpoint on the function and you will see that no one is included in the testing.

What we have just done is the preparation (Arrange phase) - the first of the three phases of the Arrange-Act-Assert (AAA) methodology that TypeMock supports [ 2 ]. Now we will deal with the second phase - Act. Here we will actually call our method, that is, we will perform an action on the system under test:

 int result = p. CalculatePay (w);

Now try to guess the result! After all, we even workHours not initialize workHours — the value is null . However, we have a very realistic result - 400. Moreover, we can even check that the GetTotalHoursWorked() method was actually called (the fact that the changed method was called does not matter). This is the last phase of AAA, namely Assert. We look:

 Assert.AreEqual (400, result);
 Isolate.Verify.WasCalledWithAnyArguments (() => w.GetTotalHoursWorked ());

Finally, take a look at the whole test [ 3 ]

 [Test]
 public void TestPayroll ()
 {
   // Arrange
   Payroll p = new Payroll ();
   Worker w = Isolate.Fake.Instance <Worker> ();
   Isolate.WhenCalled (() => w.GetTotalHoursWorked ()). WillReturn (40);
   // Act
   int result = p. CalculatePay (w);
   // Assert
   Assert.AreEqual (400, result);
   Isolate.Verify.WasCalledWithAnyArguments (() => w.GetTotalHoursWorked ());
 }

So what did we do? We tested the Payroll.CalculatePay() method, replacing the Person parameter with a certain similarity that behaved predictably and did not affect the actual properties and methods of the class.

Nonpublic


In our first example, everything was very simple - all our elements were public and therefore there were no access problems. Now let's imagine that the Worker.GetTotalHoursWorked() method is in a different assembly, and is marked as internal :

 public class Worker
 {
   private List <int> workHours {get;  set;  }
   internal int GetTotalHoursWorked () {return workHours.Sum ();  }
 }

Houston, we have a problem! Our test will no longer compile, because two lines of code using GetTotalHoursWorked() no longer have access to it:

 // will not work
 Isolate.WhenCalled (() => w.GetTotalHoursWorked ()). WillReturn (40);
 // this too
 Isolate.Verify.WasCalledWithAnyArguments (() => w.GetTotalHoursWorked ());

How to replace the non-public method? Elementary Watson! Using Isolator.NonPublic we can set the method by name:

 Isolate.NonPublic.WhenCalled (w, "GetTotalHoursWorked"). WillReturn (40);
 ...
 Isolate.Verify.NonPublic.WasCalled (w, "GetTotalHoursWorked");

That's all! In the same way as methods, it is possible to intercept references, for example, to a property or an indexer ( operator this[] ). Well, checks for calls can be done accordingly.

Statics, utilization, and other


In addition to working with objects that can be created with the new operator, TypeMock can also work with static objects. For example, to fake a static constructor, we simply call Isolate.Fake.StaticConstructor(typeof (T)); , and then use TypeMock as before. The same is done with static methods.

In addition to replacing objects with moks, TypeMock supports duck typing, that is, the ability to replace one object with another, even when their interfaces are not exactly the same. Here is a small example:

 public class Dog
 {
   public Dog () {}
   public string MakeSound ()
   {
     return "Woof";
   }
 }
 public class Duck
 {
   public string MakeSound ()
   {
     return "Quack";
   }
 }

Here we have a duck and a dog, and we naturally want the dog to grunt. In TypeMock, this is done like this:

 [TestFixture, Isolated]
 public class Tests
 {
   [Test]
   public void Test ()
   {
     // fake a dog
     Dog dog = Isolate.Fake.Instance <Dog> ();
     Duck duck = new Duck ();
     // replace calls on dog with calls on duck
     Isolate.Swap.CallsOn (dog) .WithCallsTo (duck);
     // get a dog to quack
     string sound = dog.MakeSound ();
     // did it?
     Assert.AreEqual ("Quack", sound);
     Isolate.Verify.WasCalledWithAnyArguments (() => dog.MakeSound ());
   }
 }

That's all!


I hope in this short post I showed that moki is not at all scary, and using them is easy! Thanks for attention!

Notes


  1. In order for all this to work, you need to add links to two other assemblies from the GAC to our assembly - TypeMock Isolator and TypeMock Isolator - Arrange-Act-Assert.
  2. Two other approaches - Reflective Mocks and Natural Mocks - are not considered in this essay.
  3. It should also be noted that in addition to the Test attribute, the Isolated attribute must be used either at the method level or at the class level — this attribute allows you to clear the context of the used mock objects.

St. Petersburg ALT.NET Group

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


All Articles