Announcement Announcement Module
Collapse
No announcement yet.
Single context loading for all test classes Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Single context loading for all test classes

    I would like to initialize my applicaton context only once for all of my test classes. I have found some posts that propose static initializations but this only works within one test class. Other test classes will load a new application context.

    Is there any best practise for bootstrapping the application context only once?

  • #2
    I'm working on a solution that will use a static, but will use a key so that it applies correctly across all classes. I'm currently using this with excellent results in a consulting engagement, and will check it into the mock tree next week, and release it as part of Spring 1.2.

    It also supports running tests inside a transaction that's rolled back automatically. We've found this a huge productivity gain for integration testing.

    I'll try to remember to post here when I've checked it in...

    Comment


    • #3
      I've now committed my support classes, including the transactional capability I mentioned. See the org.springframework.test package in the /mock tree in CVS.

      Feedback welcome.

      Comment


      • #4
        ok. thanks. I'll give it a shot and let you know.

        Comment


        • #5
          Below is the latest version of something I posted a little while ago.

          http://forum.springframework.org/sho...902&highlight=

          You need to call initialize() in setup of each class (in a test suite), but the context is only loaded once if the list of context files never changes. And if list changes that is ok too; context will be loaded if the file list differs from previous test class.

          I have found some posts that propose static initializations but this only works within one test class.
          ContextTestCase will load context only once for entire test suite.

          Other test classes will load a new application context.
          Not if all classes in your test suite inherit from ContextTestCase and the context file list is the same in initialize() method call of each.


          Here is latest version:

          Code:
          import java.util.StringTokenizer;
          import junit.framework.TestCase;
          import org.springframework.context.ApplicationContext;
          import org.springframework.context.support.ClassPathXmlApplicationContext;
          
          /**
           * A JUnit TestCase extension that simplifies integration testing by making 
           * available a Spring application context.  This class short-circuits the 
           * 'set all instance variables null before every test method' mechanism in 
           * favor of loading Spring environment only once before executing any test 
           * methods.
           */
          public class ContextTestCase extends TestCase 
          {
            private static String[] contextFiles = null;
            private static ApplicationContext context = null;
            
            protected final Object getBean( String bean )
            {
              return context.getBean(bean);
            }
          
            protected final ApplicationContext getContext()
            {
              return context;
            }
            
            /**
             * Alternate form to initialize/load context
             * @param xmlFiles in comma delimited string
             */
            protected final void initialize( String xmlFiles )
              throws Exception
            {
              String[] contextFiles = toStringArray( xmlFiles );
              initialize( contextFiles );
            }
            
            /**
             * @param xmlFiles  context files that specify objects available 
             *                  in Spring application environment.
             */
            protected final void initialize( String[] xmlFiles ) 
               throws Exception
            {
              synchronized (ContextTestCase.class) {
                if( context == null || !same( xmlFiles, contextFiles ) ) {
                  contextFiles = xmlFiles;
                  context = new ClassPathXmlApplicationContext( xmlFiles ); // load context
                  
                  StringBuffer msg = new StringBuffer("<<< CONTEXT LOADED >>> [");
                  for( int i = 0; i < xmlFiles.length; i++ ) {
                    if ( i > 0 ) { msg.append(" "); }
                    msg.append( xmlFiles[i] );
                  }
                  msg.append("] IN class='");
                  msg.append( this.getClass().getName() );
                  msg.append("'");
                  System.out.println( msg.toString() );
                }
              }
            }
            
            /*
             * Are String arrays the same or different?
             */
            private static boolean same( String[] files1, String[] files2 )
            {
              boolean ret = true;
              ret = files1 != null && files2 != null && files1.length == files2.length; 
              // true if both not null & same length arrays
              
              // loop stops if ret==false
              for( int i = 0; ret && i < files1.length; i++ ) { 
                ret = files1[i].equals( files2[i] );
              }
              return ret;
            }
            
            /*
             * Convert comma delimited string into String array.
             */
            private static String[] toStringArray( String listing )
            {
              String[] ret = null;
              
              StringTokenizer t = new StringTokenizer( listing, "," );
              final int count = t.countTokens();
              ret = new String[count];
              
              for( int i = 0; i < count; i++ ) {
                ret[i] = t.nextToken().trim();
              }
              return ret;
            }
          }
          And your descendent class will have code something like this:

          Code:
            MyClass instanceRef = null;
          
            public void setUp() throws Exception 
            { 
              initialize( "contextFile1.xml,contextFile2.xml" ); 
              instanceRef = (MyClass)getBean("myClassBean"); 
            }
          Last edited by Rod Johnson; Jan 18th, 2006, 10:56 AM.

          Comment


          • #6
            That works within a testsuite but the point is to make something run outside a testsuite (within an Ant build e.g.)

            Comment


            • #7
              You are saying following does not work?

              <junit fork="yes" forkmode="once">
              <test fork="no" ....
              etc....

              Where all the tests are defined in same way.

              If you can get all the tests to run in same JVM then context should be loaded once as desired.

              Regards, Jim

              Comment


              • #8
                I have tried Rod's test case and it didn't work with a standard Maven build that executes the tests. Furthermore even within Eclipse the testcases submitted in CVS reload the application context with each test class. Feester's approach works in Eclipse but doesn't in Maven. I'll first look at what's wrong with Rod's test cases (might be me not using them correctly) then when it works in Eclipse, I'll try to see if there's something wrong with Maven's test plugin.

                Rod,

                Could you add a very simple test class that illustrates the usage of your test classes? That might help me figuring out what goes wrong.

                Comment


                • #9
                  I don't know anything about Maven. Does Maven build run via Ant?

                  If so, and even if not, I believe the problem is that each test class is getting forked into a new JVM.

                  If Ant is involved, take a look at the documentation for junit and test tasks and their fork and forkmode attributes.

                  http://ant.apache.org/manual/OptionalTasks/junit.html

                  Comment


                  • #10
                    Maven uses Ant underneath. I had a quick look at the way Maven calls Ant and normally, by default, it does not fork unit tests. I'll have to dig deeper into this because I suspect that Maven is actually forking test classes.

                    Comment


                    • #11
                      In fact the "AbstractDependencyInjectionSpringContextTests " class does correctly cache the applicationcontext. The problem is that even though the applicationcontext is cached, my Hibernate session factory does get rebuild.

                      Comment


                      • #12
                        ok. I figured out a bug in the way I implemented the test class. Now I dig into Maven and see what's going on there.

                        Comment


                        • #13
                          Problem with ContextTestCase

                          Hi there.
                          First I'd like to thank feester for the class. Great job.

                          Now, the problem I am having. I am able to pass the xml files and the loading starts with no problem.
                          During the initialization of a bean with property defined as follows
                          Code:
                          <property name="location">
                               <value>/WEB-INF/my.properties</value>
                          </property>
                          I get a nested exception java.io.FileNotFoundException: Could not open class path resource [WEB-INF/my.properties].

                          If I remove the /WEB-INF/ from the value field everything works perfectly.
                          Or, without removing that, I can add my root to the classpath, but I do not want to do that.

                          I have tried to add such resource dynamically to the class path but I could not figure out a way to add it to the context.

                          Any help will be appreciated thanks.

                          Comment


                          • #14
                            ... work around.

                            Well, I think I solved my problem.

                            In case anybody is interested, the only modification I had to make was to change the ClassPathXmlApplicationContext to FileSystemXmlApplicationContext. In other words:

                            Code:
                            context = new FileSystemXmlApplicationContext &#40; xmlFiles &#41;;
                            Now the context is built from the file system using the app working directory as the base dir. So now my initilize() method looks like:
                            Code:
                            initialize&#40;"/WEB-INF/classes/file1.xml,/WEB-INF/classes/file2.xml&#41;;
                            Hope it helps.

                            Comment

                            Working...
                            X