You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

生产环境正常,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 FETCH query in your DAO to eagerly load the association when fetching ClassA:
    // 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 ClassA and ClassB
  • 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:28:25