生产环境正常,Arquillian/Junit测试报InvalidStateException(unmanaged object)
Alright, let's break down why you're hitting that InvalidStateException (unmanaged object) in your Arquillian/JUnit tests even though everything works fine in production. The core issue here is almost always related to entity lifecycle management differences between your production container and test environment, especially with that unidirectional ManyToMany relationship between ClassA and ClassB.
Here are concrete fixes you can apply, ordered by likelihood of solving your problem:
1. Ensure Associated ClassB Entities Are Persisted/Managed Before Linking to ClassA
In production, your REST endpoint operates within a container-managed transaction where new ClassB instances are automatically persisted or fetched as managed entities. But in tests, if you're just creating a new ClassB() and adding it directly to ClassA's collection without first persisting it, that ClassB stays in an unmanaged state—the EntityManager doesn't track it, hence the exception.
Fix this by explicitly persisting ClassB before associating it:
@Test @Transactional public void testClassAAssociation() { // First persist ClassB to make it a managed entity ClassB classB = new ClassB(); em.persist(classB); // Now link the managed ClassB to ClassA ClassA classA = new ClassA(); classA.getBs().add(classB); em.persist(classA); // Verify the association works without unmanaged errors ClassA persistedA = em.find(ClassA.class, classA.getId()); assertTrue(persistedA.getBs().contains(classB)); }
2. Explicitly Manage Transactions in Your Tests
Production REST endpoints typically run within an implicit container transaction, but Arquillian tests might not automatically handle this. If your test code operates outside a transaction, entities can become detached/unmanaged once the initial persistence operation finishes.
Use transactional annotations or manual transaction control:
@RunWith(Arquillian.class) public class ClassARestTest { @PersistenceContext private EntityManager em; @Inject private ClassARestEndpoint restEndpoint; @Test @Transactional // Ensures the entire test runs in a single transaction public void testGetClassA() { // Setup test data (all within transaction, so entities stay managed) ClassB classB = new ClassB(); em.persist(classB); ClassA classA = new ClassA(); classA.getBs().add(classB); em.persist(classA); // Call your REST endpoint method directly (or via client) Response response = restEndpoint.getClassA(classA.getId()); ClassA retrievedA = (ClassA) response.getEntity(); // No unmanaged exception here because we're still in the transaction assertEquals(1, retrievedA.getBs().size()); } }
If your setup doesn't support @Transactional, manually control the transaction:
@Test public void testManualTransaction() { em.getTransaction().begin(); // Create and persist entities here ClassB classB = new ClassB(); em.persist(classB); ClassA classA = new ClassA(); classA.getBs().add(classB); em.persist(classA); em.getTransaction().commit(); // If you need to fetch and modify later, start a new transaction em.getTransaction().begin(); ClassA fetchedA = em.find(ClassA.class, classA.getId()); // Do operations em.getTransaction().commit(); }
3. Validate Your CrudDao is Properly Managed in Tests
If you're manually instantiating your CrudDao in tests (e.g., new CrudDaoImpl()), it won't have access to a container-managed EntityManager, leading to unmanaged entity operations. Always inject the DAO using container annotations:
@RunWith(Arquillian.class) public class ClassADaoTest { @Inject // Use @EJB if it's a stateless bean private CrudDao<ClassA> classADao; @Test @Transactional public void testSaveClassA() { ClassB classB = new ClassB(); // If your DAO handles persistence, make sure it's using a managed EM classADao.save(classB); ClassA classA = new ClassA(); classA.getBs().add(classB); classADao.save(classA); ClassA retrieved = classADao.findById(classA.getId()); assertNotNull(retrieved.getBs()); } }
4. Handle Lazy Loading in the Test Context
If your ClassA's ClassB collection uses lazy loading (the default for ManyToMany), accessing the collection outside a transaction will cause issues—but in your case, the unmanaged object error points more to the associated ClassB never being managed. That said, to avoid related lazy loading exceptions, either:
- Access the collection within the transaction (like we did in the examples above), or
- Use a
JOIN FETCHquery in your DAO to eagerly load the association when fetchingClassA:// In your CrudDao implementation for ClassA public ClassA findByIdWithAssociations(Long id) { return em.createQuery( "SELECT a FROM ClassA a JOIN FETCH a.bs WHERE a.id = :id", ClassA.class ) .setParameter("id", id) .getSingleResult(); }
5. Align Test Container Configuration with Production
Double-check your test persistence.xml (in src/test/resources/META-INF) matches your production configuration. Ensure:
- The data source is correctly configured (use a container-managed JTA data source if production does)
- Entity scanning includes both
ClassAandClassB - Hibernate/JPA properties (like
hibernate.hbm2ddl.auto) are set appropriately for tests
Production works because the container automatically handles transactions and entity management, but tests require you to be more explicit about these controls. The steps above should resolve your unmanaged object exception.
内容的提问来源于stack exchange,提问作者Ed LaFave

