Announcement Announcement Module
Collapse
No announcement yet.
In unit testing, transactions aren't committed Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • In unit testing, transactions aren't committed

    I've spent the day slowly getting unit testing of my SpringMVC/Hibernate webapp working. The app itself works great. It uses OpenSessionInViewFilter, which I've simulated in my tests with the following:

    Code:
    @Before
    public
    void
    setup()
    {
        sLogger.debug("setup");
        
        //  Set up the Hibernate session and transaction…
        
        mSessionFactory = (SessionFactory) mAppContext.getBean("sessionFactory");
        Session session = SessionFactoryUtils.getSession(mSessionFactory, true);
        TransactionSynchronizationManager.bindResource(mSessionFactory, new SessionHolder(session));
    }
    
    @After
    public
    void
    tearDown()
    {
        sLogger.debug("teardown");
        
        SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(mSessionFactory);
        //SessionFactoryUtils.releaseSession(sessionHolder.getSession(), mSessionFactory);
        SessionFactoryUtils.closeSession(sessionHolder.getSession());
    }
    I'm using an H2 in-memory DB for the testing that I set up once with @BeforeClass. I then have three @Test methods. The first two work as expected, but the third depends on the results of the second being committed to the DB. They do not appear to be committed. The Spring configuration files I'm loading are the exact same ones used by the webapp, so the transaction handling should be the same, assuming my @Before and @After methods are doing the right thing.

    My test class is annotated with

    Code:
    @TransactionConfiguration(defaultRollback = false)
    The SessionFactory configuration is

    Code:
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan" value="com.latencyzero.gamecenter.model"/>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
                <prop key="hibernate.cache.provider_class">org.hibernate.cache.NoCacheProvider</prop>
                <prop key="hibernate.jdbc.batch_size">20</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.use_sql_comments">false</prop>
                <prop key="hibernate.generate_statistics">true</prop>
            </props>
        </property>
    </bean>
    which I thought would keep it from rolling back transactions after each test. But I can't seem to get it to commit after each test.

    Perhaps I'm trying to do things the wrong way. I intended to make a new test class for each set of tests that should run in a clean DB, but have the individual test methods within a test class depend on each other (and their execution order, which I'm assuming is lexicographical, although I am not sure).

    I'm reluctant to put an entire series of tests into one method, because I want the transaction isolation around each particular operation, and I don't think I'll get that implicitly if I put them all into one method.

    Moreover, I want the ability to use an embedded H2 DB so I can take browse the data being written. Right now, nothing gets committed.

    Any advice would be most appreciated. Thanks!
    Last edited by jetforme; Dec 6th, 2012, 07:39 AM. Reason: add SessionFactory config info

  • #2
    I'm reluctant to put an entire series of tests into one method, because I want the transaction isolation around each particular operation, and I don't think I'll get that implicitly if I put them all into one method.
    If you want that then don't use a transactional testcase, that way transactions behave as normal.

    Tests being dependend on each other and in a specific order is, at least in my book, a no go zone. Different JDKs, OSses etc. can influence that and you don't want to be depended on such a brittle thing. Put it in a single method and be done with it. I would also not use/simulate OSIV as that is something you probably only need for your web part.

    Comment


    • #3
      Originally posted by Marten Deinum View Post
      If you want that then don't use a transactional testcase, that way transactions behave as normal.
      Thanks for the quick reply, Marten.

      What makes a testcase transactional? The mere presence of @TransactionConfiguration? Initially I didn't have that, but added it after seeing it in various OSIV examples online.

      Tests being dependend on each other and in a specific order is, at least in my book, a no go zone. Different JDKs, OSses etc. can influence that and you don't want to be depended on such a brittle thing. Put it in a single method and be done with it. I would also not use/simulate OSIV as that is something you probably only need for your web part.
      Well, my tests are using MockHttpServletRequest to actually exercise my Spring MVC methods. Those have their Sessions automatically created and closed. Hence the OSIV simulation.

      I tried putting all the operations in one method, and the second operation (which is a repeat of the first operation) gets this error:

      Code:
          [junit] org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.latencyzero.gamecenter.model.Player#1]
      I think the session is not properly flushing objects.

      Comment


      • #4
        We could argue about what you are trying to do but lets not go there .

        If you are invoking the web layer then it doesn't even make sense to have a transactional testcase and 3 dependend test methods. Put it in a single method as 3 seperate invocations.

        I assume you are extending springs convenience testclasses I suggest to extend the simplest one and not the more extensive one which is transactional by default.

        If you aren't rendering a view you probably aren't going to need OSIV any way... But if you must...

        I would suggest a read/try of Spring-mvc-test which makes it easier to do testing instead of hacking around yourself .

        Comment


        • #5
          Originally posted by Marten Deinum View Post
          We could argue about what you are trying to do but lets not go there .

          If you are invoking the web layer then it doesn't even make sense to have a transactional testcase and 3 dependend test methods. Put it in a single method as 3 seperate invocations.

          I assume you are extending springs convenience testclasses I suggest to extend the simplest one and not the more extensive one which is transactional by default.

          If you aren't rendering a view you probably aren't going to need OSIV any way... But if you must...

          I would suggest a read/try of Spring-mvc-test which makes it easier to do testing instead of hacking around yourself .
          Well, I'd like to fundamentally understand what is going on, and start from the most basic of code. Adding yet another package to the mix adds stuff I'm not using in the webapp, and I don't trust it.

          My test class is very simple:

          Code:
          @RunWith(SpringJUnit4ClassRunner.class)
          @ContextConfiguration
          ({
              "file:target/build/WEB-INF/config/springContext.xml",
              "file:target/build/WEB-INF/config/springWebDispatcherConfig.xml",
          })
          public
          class
          TestServiceController
              implements com.latencyzero.gamecenter.Constants
          {
          I don't mind putting things into single @Test methods, but as I said that's also failing, but I don't know why. Each call to one of my MVC Controller methods should be bracketed in a transaction, and it would be easier for me to debug all sorts of issues if they would not be rolled back after each test case.

          Do you know what changes I need to make, other than adding yet another framework?

          Comment


          • #6
            It's not another framework it is part of spring (as of 3.2) and available as seperate project before that. But alas.

            Your tests are transactional (that is the default when not specifing which TestExecutionListeners to use) and as such each method operates in its own transaction. You want to extend the AbstractJUnit4SpringContextTests class (after which you can remove the @RunWith annotation), which disables transactional tests.

            Drawback of putting everything in a single method it also uses a single session (although that doesn't mean it is problematic but it is different compared to the behavior of your normal web application).

            However as mentioned before you are basically on a slipperly slope and you shouldn't have dependend tests (that doesn't belong in a Unit test). But that is IMHO ofcourse.

            Comment


            • #7
              Originally posted by Marten Deinum View Post
              It's not another framework it is part of spring (as of 3.2) and available as seperate project before that. But alas.

              Your tests are transactional (that is the default when not specifing which TestExecutionListeners to use) and as such each method operates in its own transaction. You want to extend the AbstractJUnit4SpringContextTests class (after which you can remove the @RunWith annotation), which disables transactional tests.

              Drawback of putting everything in a single method it also uses a single session (although that doesn't mean it is problematic but it is different compared to the behavior of your normal web application).

              However as mentioned before you are basically on a slipperly slope and you shouldn't have dependend tests (that doesn't belong in a Unit test). But that is IMHO ofcourse.
              Well, it's not part of 3.1.3 yet, and as far as I can tell, 3.2 is not yet final. I've said I'm willing to combine operations, if I can get individual sessions/transactions for each MVC method.

              Right now, it seems that tests are executed in declared order, and that's better for me now than the other stuff. This will not be run in another Java environment any time in the near future.

              It's not clear to me how to set up the TestExecutionListener to make the test non-transactional (and ensure that the transactions happen around my mock requests).

              I'm looking at the (still separate) spring-mvc-test, but AUGH there's a lot more to learn to use it.

              And I STILL haven't learned how to get my results to commit.

              Comment


              • #8
                I'm trying to follow your advice and use spring-test-mvc. Unfortunately, the presentation and docs leave quite a lot to be desired. In particular, I added an @Autowire WebApplicationContext to my test, but there isn't one instantiated anywhere. The example references a servlet-context.xml file, but it's hard to find the exact one in the github repo. Moreover, the XmlTestContextTests example, which seems to be close to what I want, references WebContextLoader.class, but doesn't import it, and I see no separate file in the same package.

                Any suggestions?
                Last edited by jetforme; Dec 6th, 2012, 08:39 AM.

                Comment


                • #9
                  Not sure if the presentation and docs are still in sync (although they should be).

                  The WebContextLoader is in the same package as the testcase you refer to (check the source if it) and classes in the same package don't need to be imported. This class is responsible for instantiating the WebApplicationContext which in turn will be injected by the test context framework in your testclass.

                  Comment


                  • #10
                    Well, Marten, after following your advice and spending the last hour getting spring-test-mvc up-and-running, I run into this:

                    Code:
                    org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.latencyzero.gamecenter.model.Game.mAuthTokens, no session or session was closed
                    I'm back to needing OSIV. I'm really no farther along than when I started.

                    How does spring-test-mvc handle this?

                    Comment


                    • #11
                      You can add your filters as normal and the request should pass through it just as your normal environment.

                      Comment


                      • #12
                        Add them where?

                        Comment


                        • #13
                          I'm now creating my test class like this:

                          Code:
                          @RunWith(org.springframework.test.context.junit4.SpringJUnit4ClassRunner.class)
                          @WebAppConfiguration("file:target/build")
                          @ContextConfiguration
                          ({
                              "file:target/build/WEB-INF/config/springContext.xml",
                              "file:target/build/WEB-INF/config/springWebDispatcherConfig.xml",
                          })
                          public
                          class
                          TestServiceController
                          {
                              ...
                          }

                          Comment


                          • #14
                            In your test method you have your test method that whips up the mockmvc stuff which also allows for adding filters or at least access to the chain to add filters.

                            Comment


                            • #15
                              Originally posted by Marten Deinum View Post
                              In your test method you have your test method that whips up the mockmvc stuff which also allows for adding filters or at least access to the chain to add filters.
                              I'm pointing the @WebAppConfiguration annotation at my web app directory. Is it not using web.xml in there to configure things?

                              I configure MockMvc like this:

                              Code:
                              @Before
                              public
                              void
                              setup()
                              {
                                  mMockMVC = MockMvcBuilders.webAppContextSetup(mWAC).build();
                              }
                              
                              @Autowired  private     WebApplicationContext   mWAC;

                              Comment

                              Working...
                              X