英文:
How to mock EntityManager
问题
我最初在这里发布了一个关于 [单元测试、EntityManager 和 NullPointerException][1] 的问题。然后有人建议我去了解模拟(mocking),所以我去了。然而,我仍然面临着相同的问题。我想知道是否有什么我忽略的东西?是否有设置我忘记添加的?我需要指示持久性 XML 文件的位置吗?对于这方面的任何帮助,我都会非常感谢。
我查看了其他类似的问题。但是,我没有太多的运气。
1./ https://stackoverflow.com/questions/47488914/how-to-mock-object-in-entitymanager
2./ https://stackoverflow.com/questions/4296042/how-to-mock-entitymanager
3./ https://stackoverflow.com/questions/29469442/mocking-entitymanager
package com.beetlehand.model.dao;
import com.beetlehand.model.AttributeEntity;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
public class AttributeDaoTest {
public AttributeEntity entity = new AttributeEntity();
private AutoCloseable closeable;
@Mock
private EntityManager entityManager;
@Test
void testGetById(){
TypedQuery<AttributeEntity> queryByMock = (TypedQuery<AttributeEntity>) Mockito.mock(TypedQuery.class);
Mockito.when(entityManager.createQuery("SELECT a FROM attributes a WHERE attribute_id = 1"))
.thenReturn(queryByMock);
Mockito.when(queryByMock.getSingleResult()).thenReturn(entity);
}
@BeforeEach
void setUp() {
closeable = MockitoAnnotations.openMocks(this);
}
@AfterEach
void tearDown() throws Exception{
closeable.close();
}
}
持久性配置文件,
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<persistence-unit name="NewPersistenceUnit">
<class>com.beetlehand.model.AttributeEntity</class>
<class>com.beetlehand.model.AttributeValueEntity</class>
<class>com.beetlehand.model.AuditEntity</class>
<!-- 其他类 -->
<properties>
<!-- 属性配置 -->
</properties>
</persistence-unit>
</persistence>
[1]: https://stackoverflow.com/questions/64405943/java-entitymanager-throwing-nullpointerexception
英文:
I first posted a question on here about a Unit test, EntityManager and NullPointerException. And then somebody advised me to look up mocking, so I did. However, I am still facing the same issues. I like to know if I am missing something? Is there a setup I forgot to add? Do I require to indicate the location of the persistence XML file? I would appreciate any help with that.
I looked at other similar questions. However, I didn’t have much luck.
1./ https://stackoverflow.com/questions/47488914/how-to-mock-object-in-entitymanager
2./ https://stackoverflow.com/questions/4296042/how-to-mock-entitymanager
3./ https://stackoverflow.com/questions/29469442/mocking-entitymanager
package com.beetlehand.model.dao;
import com.beetlehand.model.AttributeEntity;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
public class AttributeDaoTest {
public AttributeEntity entity = new AttributeEntity();
private AutoCloseable closeable;
@Mock
private EntityManager entityManager;
@Test
void testGetById(){
TypedQuery<AttributeEntity> queryByMock = (TypedQuery<AttributeEntity>) Mockito.mock(TypedQuery.class);
Mockito.when(entityManager.createQuery("SELECT a FROM attributes a WHERE attribute_id = 1"))
.thenReturn(queryByMock);
Mockito.when(queryByMock.getSingleResult()).thenReturn(entity);
}
@BeforeEach
void setUp() {
closeable = MockitoAnnotations.openMocks(this);
}
@AfterEach
void tearDown() throws Exception{
closeable.close();
}
}
Persistence configuration file,
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<persistence-unit name="NewPersistenceUnit">
<class>com.beetlehand.model.AttributeEntity</class>
<class>com.beetlehand.model.AttributeValueEntity</class>
<class>com.beetlehand.model.AuditEntity</class>
<class>com.beetlehand.model.CategoryEntity</class>
<class>com.beetlehand.model.CustomerEntity</class>
<class>com.beetlehand.model.DepartmentEntity</class>
<class>com.beetlehand.model.OrderDetailEntity</class>
<class>com.beetlehand.model.OrdersEntity</class>
<class>com.beetlehand.model.ProductEntity</class>
<class>com.beetlehand.model.ProductAttributeEntity</class>
<class>com.beetlehand.model.ProductCategoryEntity</class>
<class>com.beetlehand.model.ReviewEntity</class>
<class>com.beetlehand.model.ShippingEntity</class>
<class>com.beetlehand.model.ShippingRegionEntity</class>
<class>com.beetlehand.model.ShoppingCartEntity</class>
<class>com.beetlehand.model.TaxEntity</class>
<properties>
<property name="toplink.jdbc.url" value="jdbc:mysql://localhost:3306/beetlehand"/>
<property name="toplink.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/beetlehand"/>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="openjpa.ConnectionURL" value="jdbc:mysql://localhost:3306/beetlehand"/>
<property name="openjpa.ConnectionDriverName" value="com.mysql.jdbc.Driver"/>
<property name="eclipselink.jdbc.url" value="jdbc:mysql://localhost:3306/beetlehand"/>
<property name="eclipselink.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/beetlehand"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root" />
<property name="javax.persistence.jdbc.password" value="toor" />
</properties>
</persistence-unit>
</persistence>
答案1
得分: 1
“某人”可能是我,所以我试着澄清我或许不太清楚的评论。
正如评论所示,您可能不希望对 EntityManager
进行嘲弄。我认为这是可能的,但没有必要这样做。现在的问题更可能是您需要对“单元”测试和“集成”测试进行澄清,以及在何时执行哪种测试。
我试着做些澄清。
您有一个查询:
SELECT a FROM attributes a WHERE attribute_id = 1;
您如何测试该查询是否格式正确并且返回所需内容?您可以打开数据库 shell 并运行一些查询以查看它是否正常工作,但为了在 Java 中并作为回归测试,除了创建一个将一些测试数据插入到数据库中并应用此查询的集成测试外,别无他法。
因此,不需要对 EntityManager
进行模拟,因为您需要真实的 EntityManager
。这需要一个能够注入这个真实 EntityManager
的测试环境/框架(我过去常常使用 Arquillian,但我猜现在可能有更多选择)。
您可能已经明智地设计了您的 DAO,并且有一个类似这样的类:
@LocalBean // 不一定是本地 bean,但要声明它为一个 bean
public class MyDao {
@Resource
private EntityManager em;
public AttributeEntity getByAttributeId(Long id) {
// 返回通过您现在拥有的查询找到的 AttributEntity
}
}
然后,您将有一个类似下面的集成测试:
public class MyDaoIT {
@Resource
// EntityManager 在您的 MyDao 中,您在测试中不应再关心它
private MyDao myDao;
public AttributeEntity testGetByAttributeId1() {
// 仅作为示例
assertEquals(1L, myDao().getByAttributeId(1).getAttributeId());
}
}
上面的内容需要一些能够初始化测试容器的测试框架。
但这与模拟有什么关系呢?如果对于某个对象没有必要进行“真实”初始化,模拟也可以用于集成测试,但 EntityManager
不是这种情况。
您可以说 - 作为某种经验法则 - 模拟应用于您不想要初始化和/或测试但您的代码需要的内容。这通常适用于单元测试,在这种测试中,您不希望等待20秒来初始化测试容器以运行1毫秒的测试,并且不希望对真实数据库进行冗余测试,例如。
您还可以拥有一些使用 MyDao
的服务类,该类具有您想要测试的某些业务逻辑:
public class MyService {
@Resource
private MyDao myDao;
// 仅是一个非常糟糕的示例方法
public AttributeEntity getCopyOfAttributeEntity(Long id) {
AttributeEntity ae = myDao.getByAttributeId(id);
AttributeEntity aeCopy = ae.clone(); // 假设克隆可行 ;)
aeCopy.setAttributeId(ae.getAttributeId() + 1);
}
}
在这里,您想要测试什么?不是查询,因为您已经在集成测试中测试过了,对吧?在这里,您希望模拟您的 MyDao
并测试服务中的业务逻辑。因此,得到的测试类如下:
public class MyServiceTest {
@Mock
private MyDao myDao;
@InjectMocks
private MyService myService;
public AttributeEntity testGetCopyOfAttributeEntity1() {
// 初始化模拟内容的部分
AttributeEntity ae = new AttributeEntity();
ae.setAttributeId(1L);
doReturn(ae).when(myDao).getByAttributeId(1L);
// 您测试服务中逻辑的部分
AttributeEntity aeCopy = myService.getCopyOfAttributeEntity(1L);
assertEquals(2, aeCopy.getAttributeId());
}
}
英文:
That "someone" could have been me so I try to clarify my perhaps unclear comment.
As the comment suggests you might not want to mock EntityManager
. It is possible I think but there is no point to do that. The issue now is more likely that you need to clarify yourself about what is unit and what integration test and when to do which.
I try to clarify this a bit.
You have a query:
SELECT a FROM attributes a WHERE attribute_id = 1;
How do you test that query is well formed and it returns what you want? You might open db shell and run some queries to see it works but to have it in Java and as regression test there is no other way than create an integration test that inserts some test data to db to which you then apply this query.
So no mocking of EntityManager
because you need the real one. That requires test environment/framework that is capable to inject this real EntityManager
(I used to use Arquillian some five years ago but guess there are more than that).
You might have designed your DAO wisely and have a class like:
@LocalBean // not necessarily a local bean but something that declares it as a bean
public class MyDao {
@Resource
private EntityManager em;
public AttributeEntity getByAttributeId(Long id) {
// return the AttributEntity found by the query you now have
}
}
Then you would have integration test that looks something like:
public class MyDaoIT {
@Resource
// EntityManager is in your MyDao and you should no more be interested
// about it in your test
private MyDao myDao;
public AttributeEntity testGetByAttributeId1() {
// just as an example
assertEquals(1L, myDao().getByAttributeId(1).getAttributeId());
}
}
The above -again- needs some test framework which initializes a test container.
But what does this have to do with mocking anyway? Mocking can be used used also with integration tests if there is no point to make "real" initialization for some object but EntityManager
is not the case.
You could say - as some kind a rule of thumb - that mocking is applied to stuff you do not want to initialize and/or test but your code needs it. And Usually this is the case with unit tests where you do not want to wait 20 seconds to get test container initialize to run 1ms test and make redundant tests agains a real database, for example.
You also could have some service class that uses MyDao
and which has some business logic you want to test:
public class MyService {
@Resource
private MyDao myDao;
// just some very bad example method
public AttributeEntity getCopyOfAttributeEntity(Long id) {
AttributeEntity ae = myDao.getByAttributeId(id);
AttributeEntity aeCopy = ae.clone(); // suppose clone works ;)
aeCopy.setAttributeId(ae.getAttributeId() + 1);
}
}
What do you want to test here? Not the query since you already tested it in your integration test, right? Here you want to mock your MyDao
and test the business logic in your service. So resulting test class like:
public class MyServiceTest {
@Mock
private MyDao myDao;
@InjectMocks
private MyService myService;
public AttributeEntity testGetCopyOfAttributeEntity1() {
// The part where mock stuff is initialized
AttributeEntity ae = new AttributeEntity();
ae.setAttributeId(1L);
doReturn(ae).when(myDao).getByAttributeId(1L);
// The part you test the logic in your service
AttributeEntity aeCopy = myService.getCopyOfAttributeEntity(1L);
assertEquals(2, aeCopy.getAttributeId());
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论