How do I mock non-Virtual Methods and sealing classes?

Source: Internet
Author: User
Document directory
  • Problem
  • Search
  • Implementation ideas and technical details
  • Implementation steps
  • Notes
  • Last

For better typographical skills, I suggest you read the following in my blog:

Http://www.dozer.cc/2012/11/how-to-mock-non-virtual-method-and-sealed-class/

 

Problem

A common problem is that there are no interfaces.MockNon-Virtual Methods andSealing class?

In my previous article (Sense of Unit Testing) Introduced the unit test principles and some tips, but the code was previously written, there will always be a lot of insurmountable points, and it is impossible to change all the methods into vitrual, or all classes have interfaces.

 

Search

First search: mock non-virtual, found an article:Portal

This article mentions an artifact:TypemockIt seems that it can be implemented, but it is charged...

Let's roughly look at its principles, andPostsharp(Postsharp is usedAOPWill modify the compiled DLL files, simple and crude!

Although rough, it seems to be a good method. Other projects are compiled normally. In the test project, all the methods are added with the virtual keyword for testing. Isn't that enough?

The idea is clear. Unfortunately, all the tools are charged until you seeLao Zhao's blog.

  • Http://blog.zhaojie.me/2012/01/make-things-mockable-with-mono-cecil.html
  • Http://www.findex.cn/item.php? Id = 421685

The above link is the original text of Lao Zhao. Unfortunately, he seems to have misoperations and is replaced by another article.

The following link is reprinted by others. You can see that even though the code is not indented.

In addition, Lao Zhao's article provides a good idea, but there is no detailed operation details in the future. I have been exploring it for a long time. Therefore, the following describes the complete and specific implementation steps.

 

Implementation ideas and technical details

In fact, the mock tool for billing has provided:

  1. In the project, write your own code according to the previous design principles;
  2. After each compilation of the test project, run a program to modify the DLL that requires mock;
  3. Use Moq and other mock frameworks to dynamically generate proxy classes during runtime;

Here, only the DLL copied to the test running directory will be modified, so other projects will not be affected.

 

For technical details, we need to useMono. CecilYes. We recommend that you useNugetObtain the latest version.

Mono. Cecil can help you modify compiled DLL files.

The core code is as follows (this logic is provided by Lao Zhao and I have made some modifications ):

private static void OverWrite(string file, bool hasSymbols){    var asmDef = AssemblyDefinition.ReadAssembly(file,                           new ReaderParameters { ReadSymbols = hasSymbols });    var classTypes = asmDef.Modules                            .SelectMany(m => m.Types)                            .Where(t => t.IsClass)                            .ToList();    foreach (var type in classTypes)    {        if (type.IsSealed)        {            type.IsSealed = false;        }        foreach (var method in type.Methods)        {            if (method.IsStatic) continue;            if (method.IsConstructor) continue;            if (method.IsAbstract) continue;            if (!method.IsVirtual)            {                method.IsVirtual = true;                method.IsNewSlot = true;                method.IsReuseSlot = false;            }            else            {                method.IsFinal = false;            }        }    }    asmDef.Write(file, new WriterParameters { WriteSymbols = hasSymbols });}

You just need to encapsulate this code into a console application and run it after each compilation of the test project.

 

Detailed implementation steps source code Solution Structure

Mockhelper is the core tool and serves to modify the compiled DLL. Generally, you only need to use this tool. Other projects are only used for demonstration.

Testdll contains a sealed class and a non-virtual function, which will be used for demonstration later to convert it into mock.

The test project isMstestProject, which demonstrates how to use mockhelper.

The nunit project is also a demo test project, but it usesNunit.

 

Use of mockhelper

This console application is actually not difficult, and the core code has been posted above.

In addition, you need to copy mockhelper.exe1_mock.txt and mono. Cecil *. DLL to your test project. There are six files in total.

You can directly run the console application and input a parameter: the folder where the DLL is located. If no parameter is set, the parameter is in the running directory by default.

Write all the DLL files you need to modify to mock.txt.

 

Configure to automatically run mockhelper

The key to copying mockhelper is to make the EXE run automatically!

Here, we use the command line for generating events later.

Right-click Project-properties-generate event command line later:

"$(ProjectDir)MockHelper\MockHelper.exe"

You do not need to pass the parameter here, because the tool is running the test project, and the default running position of this project is bin/debug | release, so the DLL to be modified is below.

 

Write test code

Testdll is a non-virtual function and is a seal class:

public sealed class TestClass : TestClassBase{    public string NormalMethod()    {        return "TestClass";    }    public override string VirtualMethod()    {        return base.VirtualMethod();    }    public sealed override string SealedMethod()    {        return base.VirtualMethod();    }    public override string AbstractMethod()    {        return "TestClass";    }}public abstract class TestClassBase{    public virtual string VirtualMethod()    {        return "TestClass";    }    public virtual string SealedMethod()    {        return "TestClass";    }    public abstract string AbstractMethod();}

 

The test code is as follows:

[TestClass]public class UnitTest{    [TestMethod]    public void TestMethod1()    {        var test = new Mock<TestClass>();        test.Setup(t => t.NormalMethod()).Returns("Mock");        test.Setup(t => t.VirtualMethod()).Returns("Mock");        test.Setup(t => t.SealedMethod()).Returns("Mock");        test.Setup(t => t.AbstractMethod()).Returns("Mock");        Assert.AreEqual(test.Object.NormalMethod(), "Mock");        Assert.AreEqual(test.Object.VirtualMethod(), "Mock");        Assert.AreEqual(test.Object.SealedMethod(), "Mock");        Assert.AreEqual(test.Object.AbstractMethod(), "Mock");    }}

 

The mstest running result is as follows:

 

The nunit running result is as follows:

 

After removing this tool, the following error will be reported:

 

Notes

Don't look at the simple steps above. I made a lot of detours during configuration. Here I will share with you:

 

Be sure to use Moq and other mock frameworks

Why is automatic mock framework required? Does its core concern inherit a class?

This tool modifies the Il Code only after the code is compiled. That is to say, when writing, it is still a sealing class or a non-virtual method.

Therefore, if you write it yourself, it cannot be compiled.

Why does the automatic mock framework work?

Because these frameworks dynamically generate a class at runtime to inherit the classes that require mock. This class has been modified during running, so no error will occur.

 

Configure mstest

At the very beginning, I encountered a very tangled problem.

It works in my demo, but it keeps making mistakes in real projects.

After research, we found that in the project with errors, the test running directory is not in Bin/debug,

Instead in testresults/dozer_DOZER-PC 2012-11-27 11_11_22/out

In addition, this folder will create a new one each time you run the test. The DLL in is not copied from bin/debug, so the modified DLL in my tool does not play a role.

But why is this not in my demo? Later, we found that a later project was enabled.Test deploymentFunction, although you do not know the specific use of this function, the project that fails after cancellation is normal!

Cancel Method: Test-edit test settings-local (the other must also be configured)-deploy-Disable deployment.

Note! The configuration can take effect only after being canceled at the same time.

 

Are all test frameworks supported?

In principle, as long as you have a way to run the tool before running the test, you can support all the test frameworks.

As you can see above, the mstest and nunit configuration methods are exactly the same.

After testing, our company's automated deployment and testing framework can support this, and other environments may need some modifications and configurations, which is not very difficult.

 

Last

Project address: https://github.com/dozer47528/MockHelper

Finally, I would like to thank Lao Zhao for his ideas! Here is actually a specific implementation.

In fact, this is a helpless move. We 'd better honestly use more interfaces!

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.