Keywords: Constructor Dependency Injection and Unit Testing (to facilitate English search)
Since the blog Development Team migrated the development architecture to DDD (domain-driven development), the "constructor dependency injection" was officially used by CNBlogs. infrastructure. crossCutting. ioC (IoC container abstraction layer, which currently uses Unity by default.
By using constructors for dependency injection, you can avoid creating instances for the dependent interfaces in the Code. This task is automatically completed by the IoC container at runtime. We mainly use APS. net mvc Controller and Application Layer Service implementation.
For example, the following APS. net mvc Controller sample code:
Public class AdminController: Controller {private IBlogSiteManagementService _ blogSiteService; public AdminController (IBlogSiteManagementService blogSiteService) {_ blogSiteService = blogSiteService;} ASP. net mvc will get the implementation of IBlogSiteManagementService through the IoC container at runtime, and pass it to the AdminController constructor. (This operation is completed in the DefaultControllerFactory. Create method. For details, see ASP. net mvc source code DefaultControllerFactory. cs 259th)
Note: To enable ASP. net mvc to support dependency injection, you must implement the IDependencyResolver interface and register all the IController implementations that come with MVC to your IoC container.
Since dependency injection was successfully used, it almost became a "control" for dependency injection. When we saw the dependency, we thought about injection.
Can love also depend on injection? Declare in your mind what your favorite girl looks like, then Cupid will inject you...
Go to the topic...
The development method of the blog community team is changed not only to DDD (domain-driven development), but also to TDD (test-driven development ). With a slight dependency injection "control", when writing test code, we can't help but think of test class constructor dependency injection.
However, I tried several mainstream. NET testing frameworks (MsTest, NUnit, MbUnit, and xUnit.net). constructor with joint parameters is not supported, let alone constructor dependency injection.
I searched the internet and did not find anyone trying to solve the problem.
At the beginning of TDD, we were troubled by this problem. It seems that it is a problem that cannot be solved temporarily... Therefore, this has become a dream that cannot be reached.
Cannot be injected. You can only manually obtain the implementation instance of the dependent interface in the no-argument constructor. The sample code (xUnit.net) is as follows:
Public class MyPostList {private IBlogSiteManagementService _ blogSiteService; public MyPostList () {_ blogSiteService = IoCFactory. instance. currentContainter. resolve <IBlogSiteManagementService> ();} [Fact] public void Get_My_Recent_Admin_Posts () {Assert. notNull (_ blogSiteService) ;}} starting yesterday, we decided to overcome this problem, compared several test frameworks, and finally chose xUnit.net (MsTest was not considered because it was not open source ).
The reason is that xUnit.net is developed by NUnit developers and has good scalability.
The solution is simple: Find out where the test class is instantiated during testing.
With the source code, this search process is much easier.
XUnit.net is also loaded through TestDriven.net in Visual Studio, so open the xUnit source code, go straight to the topic, go to the xunit. runner. tdnet project, open TdNetRunner. cs, and then follow the steps below:
Xunit. testRunner ()> Xunit. executorWrapper. runClass ()> Xunit. sdk. executor. runTests ()> TestClassCommandRunner. execute ()> TestCommandFactory. make ()> LifetimeCommand. execute () in Xunit. sdk. lifetimeCommand. execute (object testClass) found melons:
Public override MethodResult Execute (object testClass) {if (testClass = null) testClass = method. CreateInstance ();} method. CreateInstance () actually calls the code like this:
Public object CreateInstance () {return Activator. CreateInstance (method. ReflectedType);} at first glance, I can see why only constructors without parameters are supported.
When you see it, you can easily find a solution. Since testClass = null, the test class instance will be created, if we use the IoC container to create the testClass instance and pass the LifetimeCommand before executing this method. execute method, can it solve the problem?
Therefore, continue to find out where the LifetimeCommand. Execute (object testClass) method is called...
In Xunit. Sdk. TestClassCommandRunner. Execute (), find:
MethodResult methodResult = command. Execute (testClassCommand. ObjectUnderTest); The testClassCommand type is the ITestClassCommand interface. The implementation of this interface is Xunit. Sdk. TestClassCommand. Check its ObjectUnderTest attributes:
Public object ObjectUnderTest {get {return null;} as long as an instance of the test class is returned from the IoC container, dependency injection can be implemented.
Go to solution...
Note: The solution does not need to modify any source code of xUnit.net.
Create an IocTestClassCommand class that implements the ITestClassCommand interface in the unit test project. The Code is as follows:
Public class IocTestClassCommand: ITestClassCommand {TestClassCommand _ testClassCommand; public IocTestClassCommand (): this (ITypeInfo) null) {} public IocTestClassCommand (Type typeUnderTest): this (Reflector. wrap (typeUnderTest) {} public IocTestClassCommand (ITypeInfo typeUnderTest) {_ testClassCommand = new TestClassCommand (typeUnderTest);} public object ObjectUnderTest {get {return IoCFactory. inst Ance. currentContainter. resolve (_ testClassCommand. typeUnderTest. type) ;}} public Random Randomizer {get {return _ testClassCommand. randomizer;} set {_ testClassCommand. randomizer = value ;}} public ITypeInfo TypeUnderTest {get {return _ testClassCommand. typeUnderTest;} set {_ testClassCommand. typeUnderTest = value ;}[ SuppressMessage ("Microsoft. design "," CA1062: Validate arguments of public metho Ds ", MessageId =" 0 ", Justification =" This parameter is verified elsewhere. ")] public int ChooseNextTest (ICollection <IMethodInfo> testsLeftToRun) {return _ testClassCommand. randomizer. next (testsLeftToRun. count);} public Exception ClassFinish () {return _ testClassCommand. classFinish ();} public Exception ClassStart () {return _ testClassCommand. classStart ();} public IEnumerable <ITestCommand> EnumerateTestC Ommands (IMethodInfo testMethod) {return _ testClassCommand. enumerateTestCommands (testMethod);} public IEnumerable <IMethodInfo> EnumerateTestMethods () {return _ testClassCommand. enumerateTestMethods ();} public bool IsTestMethod (IMethodInfo testMethod) {return _ testClassCommand. isTestMethod (testMethod) ;}} the red font is the code for implementing dependency injection. Others are used to implement the ITestClassCommand interface and forward method calls to Xunit. sdk. testClassCommand.
The key issue has not been solved yet. There are two problems to be solved:
1. How to Use the custom IocTestClassCommand?
2. How to register the implementation of the interface that the IoC container depends on?
We need to define an Attribute inherited from Xunit. RunWithAttribute, named IoCTestClassCommandAttribute. The Code is as follows:
Public class Identifier: RunWithAttribute {public IoCTestClassCommandAttribute (): base (typeof (IocTestClassCommand) {IBootStrapper bootStrapper = new defabootbootstrapper (); bootStrapper. boot () ;}} bootStrapper. to solve the second problem, Boot registers the IoC container. BootStrapper is an independent project. The ASP. net mvc project also calls this interface to register the IoC container. This achieves the reuse of IoC registration between the unit test project and the Web project.
Ioctestclasscommandattricommand transfers the type information of IocTestClassCommand to the parent class RunWithAttribute In the constructor, solving the first problem.
Enter an exciting moment...
How to use this feature in the test class?
[IoCTestClassCommand] public class MyPostList {private region _ blogSiteService; public MyPostList (Region blogSiteService) {_ blogSiteService = region;} [Fact] public void upload () {Assert. notNull (_ blogSiteService) ;}} you only need to add the [IoCTestClassCommand] attribute to the test class.
Run the test:
Great success! As long as you stick to it, your dream will always become a reality!