在使用@Retention、@Transactional、@Inherited 注释服务进行测试后,TestNG 单元测试不起作用
Posted
技术标签:
【中文标题】在使用@Retention、@Transactional、@Inherited 注释服务进行测试后,TestNG 单元测试不起作用【英文标题】:TestNG unit test not working after annotating service to test with @Retention, @Transactional, @Inherited 【发布时间】:2015-09-27 16:58:36 【问题描述】:我正在使用 TestNG 测试业务服务,在 Spring Boot 应用程序中进行模拟单元测试。
应用程序是多模块spring boot项目。我正在为业务模块编写单元测试。
我在pom中添加了以下依赖相关的测试,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>$testng.version</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
<version>$javaxel.version</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.servlet</artifactId>
<version>$javax.servlet.version</version>
<scope>test</scope>
</dependency>
我的包装注释看起来像
@Service
@Transactional
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyServiceAnnotation
我的 TestApp 看起来像
@SpringBootApplication
public class TestApp ....
我的业务服务看起来像
@MyServiceAnnotation
public class AddressServiceImpl implements AddressService
@Autowire
UserDAO userDAO;
@Autowire
AddressDAO addressDAO;
public Address find(int userId)
user = userDAO.findOne(userId);
/** if I run following test then I get user NULL.
But it should get user object which I have created
in data provider
**/
if(user == null ) throw new BadReqExcp("invalid user Id", 101);
address = user.findAddresses();
if(address is empty) throw new BadReqExcp("add not found", 102);
return address;
MyTestClass 看起来像
@ContextConfiguration(classes = TestApp.class )
class MyTestClass
@Mock
UserDAO userDAO;
@InjectMocks
@Autowire
AddressService addressServie;
@BeforeMethod
public void initMock()
MockitoAnnotations.initMocks(this);
@Test(dataProvider = "getUser", dataProviderclass = UserDP.class)
public void shouldThrowExceptionAddressNotFound(int userId, User user)
when(userDAO.findOne(userId)).thenReturn(user); //here dao call should return user but it is returning null
try
addressService.find(userId);
catch(BadReqExcp e)
// Here errro code should be 102 but fount 101
assertEquals(e.getErrorCode(), 102);
如果我不使用@Target(ElementType.TYPE)
、@Retention(RetentionPolicy.RUNTIME)
、@Inherited
这些注释,那么我在测试中的模拟 DAO 调用可以正常工作。
我需要上面的注释,因为如果我不使用它们,那么,
例如,如果我想执行一个使用多个业务服务的单个任务,那么 它们不会发生在一个事务中。
换句话说,如果一个业务呼叫使用多个业务服务,请说ServiceA
和ServiceB
。电话从serviceA
转到serviceB
。如果serviceB
发生异常,则serviceA
所做的数据库更改不会回滚。
当我使用上面的注释时,上面的例子可以工作,但是在 junit 测试中模拟 DAO 调用不起作用。
我在 pom 中是否有错误的依赖项?
-
为什么这不起作用?
解决办法是什么?
Git Repository Source Code ,在这里您将获得示例代码。它在编译时给了我一些错误。
【问题讨论】:
你在哪里使用@MyService
?
为我所有的商业服务,例如。 @MyService 类地址服务 。请查看更新后的问题
那么你得到的异常是什么?
没有这样的例外,但正如您在服务中看到的那样。如果 usernot found 我抛出我自己的异常 Usernotfound 因此断言失败
但是 userDAO 方法运行得很好吗?我的意思是你得到一个 usernotfound 异常,但这意味着你的 DAO 运行良好,也许只是用户真的不存在?因为首先我认为您的 userDAO bean 不存在,但这是一个不同的问题。
【参考方案1】:
我建议您保持测试简单。您可以从 DI 收益中获利。更多详情请访问Spring documentation:
依赖注入的主要优点之一是它应该使您的代码更容易进行单元测试。您可以使用 new 运算符简单地实例化对象,甚至无需涉及 Spring。您还可以使用模拟对象而不是真正的依赖项。
您的测试类应该如下所示。
public class AddressTest
@Mock
private UserDAO userDAO;
@Mock
private AddressDAO addressDAO;
@InjectMocks
private AddressService addressServie;
@BeforeMethod
public void initMock()
addressServie = new AddressServiceImpl();
MockitoAnnotations.initMocks(this);
@Test(dataProvider = "getUser", dataProviderClass = UserDP.class)
public void shouldThrowExceptionAddressNotFound(int userId, User user)
when(userDAO.findOne(userId)).thenReturn(user);
try
addressServie.findAllAddress(userId);
catch (BadRequestException badRequestException)
assertEquals(badRequestException.getErrorCode(), 102);
您还应该在您的实现中检查空地址列表。测试失败,因为提供者类为测试提供了一个未初始化地址列表的用户实例。
@Override
public List<Address> findAllAddress(int userId)
User user = userDAO.findOne(userId);
if (user == null)
throw new BadRequestException("Invalid user id", 101);
List<Address> addresses = user.getAddresses();
if (addresses == null || addresses.isEmpty())
throw new BadRequestException("Address Not found", 102);
return addresses;
【讨论】:
只有一件事,如果不使用@Target(ElementType.TYPE)、@Retention(RetentionPolicy.RUNTIME)、@Inherited,为什么它们会起作用。 测试摆脱了所有这些,只测试类方法。由于初始化了一个类的实例并且 Mockito 注入了虚假的依赖项(使用 @Mock 和 @InjectMocks),因此您不需要其他任何东西。一旦应用程序在服务器上运行,您提到的所有注释都会变得相关。【参考方案2】:您可以尝试使用MockitoJUnitRunner。
@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(classes = TestApp.class )
class MyTestClass
..
【讨论】:
【参考方案3】:删除所有注释。您需要一些特别的东西才能使交易正常进行。
问题:
调用从 serviceA 转到 serviceB。如果发生异常 serviceB 然后由 serviceA 完成的数据库更改不会回滚
Spring 的事务管理器提供了一个独立于技术的 API,让您可以开始 通过调用 getTransaction() 方法创建一个新事务并由
管理它提交() 回滚()
由于 PlatformTransactionManager 是一个抽象单元 事务管理,
您为事务管理调用的方法是有保证的 独立于技术。
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
public class TransactionalJdbcBookShop extends JdbcDaoSupport implements BookShop
@Autowired
private PlatformTransactionManager transactionManager;
.....
然后在你的 dao 方法中你可以配置提交和回滚方法。
public void purchase(String isbn, String username)
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try
//Your query over here
transactionManager.commit(status);
catch (DataAccessException e)
//if the above query fails then
transactionManager.rollback(status);
throw e;
事务管理器在 XML 配置文件中被声明为普通 bean。
对于 例如,
以下 bean 配置声明了一个 DataSourceTransactionManager 实例。
它需要设置 dataSource 属性,以便它可以管理建立连接的事务 通过这个数据源。
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="bookShop"
class="com.apress.springrecipes.bookshop.TransactionalJdbcBookShop">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
</bean>
How can I use Spring Boot auto-configured beans in XML configuration files?
您也可以通过 github 通过here在您的应用程序中实现 bean
一旦有了事务定义,
您可以通过调用 getTransaction() 方法要求事务管理器使用该定义启动一个新事务。
然后它会 返回一个 TransactionStatus 对象以跟踪交易状态。
如果所有语句 执行成功,你要求事务管理器通过传递来提交这个事务 在交易状态。
由于 Spring JDBC 模板抛出的所有异常都是子类 DataAccessException,当捕获到这种异常时,您要求事务管理器回滚事务。
在这个类中,你已经声明了通用类型的事务管理器属性 平台事务管理器。
现在你必须注入一个合适的事务管理器 执行。
当您只处理单个数据源并使用 JDBC 访问它时, 您应该选择 DataSourceTransactionManager。
【讨论】:
【参考方案4】:我认为这个问题可能是注释处理顺序引起的。
您可能可以尝试在 before 方法中显式设置服务的内部状态,如下所示:
@Mock
UserDAO userDAO;
@Autowire
AddressService addressServie;
@BeforeMethod
public void initMock()
MockitoAnnotations.initMocks(this);
// using mockito Whitebox
org.mockito.internal.util.reflection.Whitebox.setInternalState(addressServie, "userDAO", userDAO);
/* or using spring test method
org.springframework.test.util.ReflectionTestUtils.setField(addressServie, "userDAO", userDAO);*/
并检查是否仍然出现错误
【讨论】:
它说“运行时无法设置内部状态”。与使用 SpringBootApplication 进行测试一样。虽然我只需要一个上下文,但这是一个问题吗?如何在没有 SpringBoot 应用程序的情况下只使用一个上下文。以上是关于在使用@Retention、@Transactional、@Inherited 注释服务进行测试后,TestNG 单元测试不起作用的主要内容,如果未能解决你的问题,请参考以下文章
在使用@Retention、@Transactional、@Inherited 注释服务进行测试后,TestNG 单元测试不起作用