How does the integration of JUnit and spring--junit testcase automatically inject a spring container-managed Object

Source: Internet
Author: User
Tags constructor event listener testng
problem

In Java, JUnit is generally used as the unit test framework, and the objects to be tested are typically service and DAO, or Remoteservice and controller. All of these test objects are basically spring-managed and are not directly new. Each TestCase class is created by JUnit. How to inject these dependencies into each testcase instance. expected effect

We want to achieve this effect:

Package me.arganzheng.study;

Import static org.junit.assert.*;
Import Org.junit.Test;
Import org.springframework.beans.factory.annotation.Autowired;

/**
 * @author Arganzheng */public
class fooservicetest{

    @Autowired
    Private Fooservice Fooservice;

    @Test public
    void Testsavefoo () {
        foo foo = new Foo ();
        // ...
        Long id = fooservice.savefoo (foo);

        Asserttrue (ID > 0);
    }
}
Solution Ideas

In fact, in my previous article: Quartz and Spring's integration of-quartz in the job how to automatically inject the spring container managed object, has discussed this issue in detail. Quartz is a framework, and JUnit is also a framework, and spring uses a very consistent approach to accessing external frameworks. For dependency injection, this is the step:

First, find the place where the external framework created the instance (class or interface), such as Quartz's jobfactory, which defaults to Org.quartz.simpl.SimpleJobFactory, Can also be configured as Org.quartz.simpl.PropertySettingJobFactory. All two of these classes implement the Org.quartz.spi.JobFactory interface. For junit4.5+, it is the Createtest method in the Org.junit.runners.BlockJUnit4ClassRunner class.

/**
  * Returns a new fixture for running a test. Default implementation executes
  * The test class ' s no-argument constructor (validation should has ensured
  * one E xists).
  *
 /protected Object createtest () throws Exception {
     return Gettestclass (). Getonlyconstructor (). newinstance () ;
 }

Inherit or combine these framework classes if you need to use some of the methods that they encapsulate. If these classes have implementation interfaces, they can also implement interfaces directly, in parallel with them. It then makes a dependency injection on the created object.

For example, in Quartz, Spring uses a method that implements the Org.quartz.spi.JobFactory interface directly:

    public class Springbeanjobfactory extends Adaptablejobfactory implements Schedulercontextaware {
        ...
    }

    public class Adaptablejobfactory implements Jobfactory {
        ...
    }

But spring's org.springframework.scheduling.quartz.SpringBeanJobFactory does not automatically rely on injection, it is also simple to create a class directly based on the job class name:

    /**
     * Create An instance of the specified job class.
     * <p>can is overridden to post-process the job instance.
     * @param bundle the Triggerfiredbundle from which the Jobdetail
     * and other info relating to the trigger firing can is Obtained
     * @return The job instance
     * @throws Exception If job instantiation failed */
    protected Object C Reatejobinstance (Triggerfiredbundle bundle) throws Exception {
        return Bundle.getjobdetail (). Getjobclass (). Newinstance ();
    }

But as the note says, Can be overridden to post-process the job instance, It is also our practice to inherit the org.springframework.scheduling.quartz.SpringBeanJobFactory and then overwrite it with this method:

public class Ourspringbeanjobfactory extends org.springframework.scheduling.quartz.springbeanjobfactory{
    @ Autowire
    private autowirecapablebeanfactory beanfactory;

    /**
     * Here we cover the Super Createjobinstance method, and then autowire the class it creates.
     *
    /@Override protected Object createjobinstance (Triggerfiredbundle bundle) throws Exception {
        Object jobinstance = super.createjobinstance (bundle);
        Beanfactory.autowirebean (jobinstance);
        return jobinstance;
    }
}

Since Ourspringbeanjobfactory is configured in the Spring container, the default is to have the ability to get applicationcontext. Of course, you can do anything that applicationcontext can do. Off Topic

Here is a very important principle of framework design: open and close principle-closed for modification, opening for expansion.
unless it is a bug, the source code of the framework will not be directly modified, but for functional personalization needs, the framework should allow users to expand.
This is why all frameworks are basically interface-oriented and polymorphic implementations, and support applications to register custom implementation classes through configuration items,
such as quartz ' org.quartz.scheduler.jobFactory.class ' and ' Org.quartz.scheduler.instanceIdGenerator.class ' Configuration items.
Solution Solutions

Back to JUnit, in fact.

junit4.5+ is to create a unit test class object through the Createtest method in Org.junit.runners.BlockJUnit4ClassRunner.

/**
 * Returns a new fixture for running a test. Default implementation executes
 * The test class ' s no-argument constructor (validation should has ensured
 * one E xists).
 */
protected Object createtest () throws Exception {
   return Gettestclass (). Getonlyconstructor (). newinstance ( );
}

So, according to the previous discussion, we just have to extendsorg.junit.runners.BlockJUnit4ClassRunner the class and cover its Createtest method. If our class can easily get to ApplicationContext (which is actually very simple, such as using Classpathxmlapplicationcontext), then it is convenient to implement the dependency injection function. JUnit does not specifically define an interface to create an UT instance, but it provides @runwith annotations that allow us to specify our own custom Classrunner. So the solution came out. Spring Built-in solutions

SPRING3 provides the Springjunit4classrunner base class so that we can easily access the JUNIT4.

public class Org.springframework.test.context.junit4.SpringJUnit4ClassRunner extends Org.junit.runners.BlockJUnit4ClassRunner {
    ...
}

The

idea is the same as we discussed above, but it uses a more flexible design: The Spring TestContext framework is introduced to allow access to different UT frameworks (such as junit3.8-,junit4.5+,testng,etc.). In contrast to the Applicationcontextaware interface, it allows you to specify the location of the configuration file to be loaded, enabling finer-grained control while caching the application context per Test Feature. This is exposed to the user through @contextconfiguration annotations. (In fact, since Springjunit4classrunner was created by JUnit instead of spring, this applicationcontextaware should not work.) But I found that abstractjunit38springcontexttests is the implementation of Applicationcontextaware interface, But its applicationcontext is also loaded by Org.springframework.test.context.support.DependencyInjectionTestExecutionListener. It feels like there is no need to implement the Applicationcontextaware interface. ) is based on the event listener mechanism (the listener-based test context framework) and allows the user to customize the event listener by registering with @testexecutionlisteners annotations. The default is Org.springframework.test.context.support.DependencyInjectionTestExecutionListener, Org.springframework.test.context.support.DirtiesContextTestExecutionListener and Org.springframework.test.context.transaction . Transactionaltestexecutionlistener these three event listeners.

The dependency injection is done in Org.springframework.test.context.support.DependencyInjectionTestExecutionListener:

/** * Performs dependency injection and bean initialization for the supplied * {@link TestContext} as described in * {@
 Link #prepareTestInstance (TestContext) preparetestinstance ()}. * <p>the {@link #REINJECT_DEPENDENCIES_ATTRIBUTE} would be subsequently removed * from the test context, regardless
 of its value. * @param testContext The test context for which dependency injection should * be performed (never <code>null</co de>) * @throws Exception allows any Exception to propagate * @see #prepareTestInstance (TestContext) * @see #beforeTe Stmethod (TestContext) */protected void Injectdependencies (final TestContext TestContext) throws Exception {Object b
    EAN = Testcontext.gettestinstance ();
    Autowirecapablebeanfactory beanfactory = Testcontext.getapplicationcontext (). Getautowirecapablebeanfactory ();
    Beanfactory.autowirebeanproperties (Bean, autowirecapablebeanfactory.autowire_no, false); Beanfactory.initializebean (Bean, Testcontext.gettestclass (). GetName ());
Testcontext.removeattribute (Reinject_dependencies_attribute);
 }

This applicationcontext is loaded into the TestContext at the time the test class was created, based on the location of the @contextlocation label:

/** * TestContext encapsulates the context in which a test are executed, agnostic of * The actual testing framework in US E. * * @author Sam Brannen * @author Juergen Hoeller * @since 2.5 */public class TestContext extends Attributeacces 
        Sorsupport {TestContext (class<?> TestClass, Contextcache Contextcache, String defaultcontextloaderclassname) { ... if (! Stringutils.hastext (Defaultcontextloaderclassname)) {defaultcontextloaderclassname = STANDARD_DEFAULT_CONTEXT
        _loader_class_name;
        } contextconfiguration contextconfiguration = Testclass.getannotation (Contextconfiguration.class);
        string[] locations = null;

        Contextloader contextloader = null;

        ... class<? Extends contextloader> Contextloaderclass = Retrievecontextloaderclass (TestClass, Defaultcontextloaderclass
        Name);
        Contextloader = (contextloader) beanutils.instantiateclass (Contextloaderclass); Locations =Retrievecontextlocations (Contextloader, TestClass);
        This.testclass = TestClass;
        This.contextcache = Contextcache;
        This.contextloader = Contextloader;
    This.locations = Locations;
 }
}
Description:

Spring3 uses the Spring TestContext Framework framework and supports multiple access methods: 10.3.5.5 TestContext support classes. Very good official documentation, highly recommended for reading. Briefly summarized as follows: Junit3.8:package org.springframework.test.context.junit38 abstractjunit38springcontexttests ApplicationContext abstracttransactionaljunit38springcontexttests ApplicationContext simpleJdbcTemplate Junit4.5:package ORG.SPRINGFRAMEWORK.TEST.CONTEXT.JUNIT4 abstractjunit4springcontexttests ApplicationContext Abstracttransactionaljunit4springcontexttests applicationcontext simplejdbctemplate Custom JUnit 4.5 Runner:springjunit4classrunner @Runwith @ContextConfiguration @TestExecutionListeners testng:package Org.springframework.test.context.testng abstracttestngspringcontexttests ApplicationContext Abstracttransactionaltestngspringcontexttests ApplicationContext Simplejdbctemplate

Add: For junit3,spring2.x originally provides three kinds of access way: abstractdependencyinjectionspringcontexttests Abstracttransactionalspringcontexttests abstracttransactionaldatasourcespringcontexttests

But starting with Spring3.0, these classes are org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests and Abstracttransactionaljuni. T38springcontexttests replaced the following:

@deprecated as of Spring 3.0, in favor of using the listener-based test context framework (although Beforetestclass and A are not supported by junit3.x Ftertestclass, so these two events cannot be monitored. )

({@link org.springframework.test.context.junit38.a

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.