Getting LazyInitializationException on JUnit Test Case
Asked Answered
D

4

29

Problem running JUnit Test inside a Spring MVC Application. Test 1 (insertTweet) seems to run fine, however on Test 2 I get an "LazyInitializationException" Exception (see full stactrace below). I understand why it is thrown but not sure why the session is closing and how to reopen it at the begining of every test 2 (or keep the existing session open for the remaining tests to complete)? I have pasted the entire StackTrace thrown along with Test Classes.

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.project.user.User.tweets, could not initialize proxy - no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:566)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:186)
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:545)
    at org.hibernate.collection.internal.AbstractPersistentCollection.write(AbstractPersistentCollection.java:370)
    at org.hibernate.collection.internal.PersistentBag.add(PersistentBag.java:291)
    at com.project.core.tweet.Tweet.<init>(Tweet.java:113)
    at com.project.core.service.impl.FanoutServiceTester.insertTweet(FanoutServiceTester.java:69)
    at com.project.core.service.impl.FanoutServiceTester.testInsertRetweet(FanoutServiceTester.java:62)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({ServiceTestExecutionListener.class})
@ActiveProfiles("test")
public abstract class AbstractServiceImplTest extends AbstractTransactionalJUnit4SpringContextTests {

    @PersistenceContext
    protected EntityManager em;

    @Autowired protected TweetService tweetService;
    @Autowired protected UserService userService;

}


public class FanoutServiceTester extends AbstractServiceImplTest{
    private static User user = null;
    private static User userTwo = null;



    @Before
    public void setUp() throws Exception {

        user = userService.findByUserId(1);


        //UserTwo Follows User One
        userTwo  = userService.findByUserId(2);



    }


    @Test
    public final void testInsertTweet() {       
        insertTweet();

        //Assert Here

    }



    @Test
    public final void testInsertRetweet() {
        insertTweet();
        //Assert Here

    }


    private Tweet insertTweet(){
        Tweet tweet = new Tweet(user);
        String text = "This is a Message";  
        tweet.setTweetText(text);
        Tweet saved = tweetService.save(tweet);
        return saved;
    }

}
Delogu answered 6/11, 2013 at 13:34 Comment(1)
What do you expect us to do with what you've shown? Post your config and your test.Candiscandle
M
7

I've run into this and the error is a bit misleading. The Session is not being closed.

When you call userService.findByUserId(1); it probably does a Join to a Tweets table and so you get this collection back:

com.project.user.User.tweets

Hibernate does not initialize this collection by default. To initialize it, you can call Hibernate.initialize() for example, like this:

Hibernate.initialize(user.getTweets());

Of course substitute getTweets() for the actual method that returns the tweets collection.

Magill answered 6/11, 2013 at 21:39 Comment(9)
That seems to work. However I'm working with JPA 2 and having "Hibernate.initialize()" inside a JPA Entity Class seems odd. Could it be that junit is calling hibernate instead of jpa?Delogu
Yes that would be odd :-) Then the Tweets collection in the entity needs to be fetched eagerly (it will be lazy, i.e. retrieved but not initialized, by default). Try annotating with @OneToMany(fetch=FetchType.EAGER)Magill
I'm not sure what you mean? Unless I'm missing something, If I explicitly define it to "Eagerly" Load the Collection, I expect it to do just that. Besides, I'd like to exhaust all other options first.Delogu
What's wrong with eagerly loading the collection in this case?Magill
Tweets Collection may contain a lot of records. If I was to "Eagerly" Load this Collection then every time I load the "User" Object, it will also load the Tweets Collection, which can lead to performance issues.Delogu
You are still using Hibernate in the project, right? Since Hibernate falls back on JPA where JPA can do the job, i.e. JPA Entities, I think it would still be ok to initialize the collection with Hibernate.initialize() right before you need to access it in your test case. I don't think JPA has an initialize collection method.Magill
Aren't you accessing the tweets somewhere in your code? i.e. when you retriever the user, user.getTweets().get(0) or something along those lines?Magill
I added the line in method @Before which seems to work. I wonder, if this Exception is thrown when running the actual application on server.Delogu
I would expect it whenever a lazily fetched collection is accessed before being initialized, just take care to initialize it before using it and it will not throw the exception on the serverMagill
P
89

add the @Transactional on your test methods – ben75 Nov 6 '13 at 14:29

Yeah, I have solved the problem:

        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration(locations = { "classpath*:applicationContext.xml"})
        @Transactional
        public class UserinfoActionTest extends StrutsSpringJUnit4TestCase<UserinfoAction> {
                @Test
            public void testLogin2(){
                          request.setParameter("jsonQueryParam", param);
                String str = null;
        try {

                    str = executeAction("/login.action");
                    HashMap<String,List<TUserinfo>> map = (HashMap<String,List<TUserinfo>>)findValueAfterExecute("outDataMap"); }
 catch (Exception e) {
                    e.printStackTrace();
                }

            }


        @Controller
        @Results({  
            @Result(name="json",type="json"
                    , params={"root","outDataMap","excludeNullProperties","true"
                            ,"excludeProperties","^ret\\[\\d+\\]\\.city\\.province,^ret\\[\\d+\\]\\.enterprise\\.userinfos","enableGZIP","true"
                    })
        })
        public class UserinfoAction extends BaseAction {
                    @Action(value="login")

            public String login(){
                if(jsonQueryParam!=null && jsonQueryParam.length()>0)
                {
                    user = JsonMapper.fromJson(jsonQueryParam, TUserinfo.class);
                }
                Assert.notNull(user);
                 //RESULT="ret" addOutJsonData: put List<TUserinfo> into outDataMap with key RESULT for struts2 JSONResult  
                addOutJsonData(RESULT, service.login(user));
                return JSON;
            }
Prindle answered 8/1, 2014 at 2:10 Comment(2)
Interesting discussion of transactional: marcobehler.com/2014/06/25/should-my-tests-be-transactionalOppress
Having @Transactional in integration test is a nightmare, which can lead to wrongly evaluated tests. Just avoid it and do your test as it should be. kode-krunch.com/2021/07/hibernate-traps-transactional.htmlPaleoecology
B
14

You are missing the TransactionalTestExecutionListener. It is required and present by default when you don't define any TestExecutionListener yourself. But as soon as you define one explicitely : it is removed.

So declare it :

@TestExecutionListeners({ServiceTestExecutionListener.class, TransactionalTestExecutionListener.class})

See "Testing - Transaction management" of the 3.2.x Spring docs.

Barret answered 6/11, 2013 at 13:56 Comment(4)
I made the changes as suggested but I am still getting the mentioned exception.Delogu
add the @Transactional on your test methodsBarret
I added that too. I think all that is not necessary if you're extending AbstractTransactionalJUnit4SpringContextTests Class, which I am doing.Delogu
I got the same issue fixed in JUnit test case by just adding TransactionalTestExecutionListener to the listeners. Thanks. :)Tuff
L
12

Enjoy

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional // The magic is here
public class YourClassTests {

...

}
Lysozyme answered 11/11, 2019 at 16:39 Comment(1)
No, please no. I am finding this in projects, that are done by try-and-let-see-what-it-does developers.Paleoecology
M
7

I've run into this and the error is a bit misleading. The Session is not being closed.

When you call userService.findByUserId(1); it probably does a Join to a Tweets table and so you get this collection back:

com.project.user.User.tweets

Hibernate does not initialize this collection by default. To initialize it, you can call Hibernate.initialize() for example, like this:

Hibernate.initialize(user.getTweets());

Of course substitute getTweets() for the actual method that returns the tweets collection.

Magill answered 6/11, 2013 at 21:39 Comment(9)
That seems to work. However I'm working with JPA 2 and having "Hibernate.initialize()" inside a JPA Entity Class seems odd. Could it be that junit is calling hibernate instead of jpa?Delogu
Yes that would be odd :-) Then the Tweets collection in the entity needs to be fetched eagerly (it will be lazy, i.e. retrieved but not initialized, by default). Try annotating with @OneToMany(fetch=FetchType.EAGER)Magill
I'm not sure what you mean? Unless I'm missing something, If I explicitly define it to "Eagerly" Load the Collection, I expect it to do just that. Besides, I'd like to exhaust all other options first.Delogu
What's wrong with eagerly loading the collection in this case?Magill
Tweets Collection may contain a lot of records. If I was to "Eagerly" Load this Collection then every time I load the "User" Object, it will also load the Tweets Collection, which can lead to performance issues.Delogu
You are still using Hibernate in the project, right? Since Hibernate falls back on JPA where JPA can do the job, i.e. JPA Entities, I think it would still be ok to initialize the collection with Hibernate.initialize() right before you need to access it in your test case. I don't think JPA has an initialize collection method.Magill
Aren't you accessing the tweets somewhere in your code? i.e. when you retriever the user, user.getTweets().get(0) or something along those lines?Magill
I added the line in method @Before which seems to work. I wonder, if this Exception is thrown when running the actual application on server.Delogu
I would expect it whenever a lazily fetched collection is accessed before being initialized, just take care to initialize it before using it and it will not throw the exception on the serverMagill

© 2022 - 2024 — McMap. All rights reserved.