使用@Retention、@Transactional、@Inherited 注释服务进行测试后,TestNG 单元测试无法正常工作

TestNG unit test not working after annotating service to test with @Retention, @Transactional, @Inherited

我正在使用 TestNG 测试业务服务,spring 启动应用程序中的 mockito 单元测试。

应用程序是多模块的spring启动project.And我正在为业务模块编写单元测试。

我在 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 调用可以正常工作。

我明确需要上面的注释,因为如果我不使用它们,

例如,如果我想执行一个使用多个业务服务的单一任务,那么它们不会在一个事务中发生。 换句话说,如果一个业务电话使用多个业务服务,则说 ServiceAServiceB。通话从 serviceA 转到 serviceB。如果 serviceB 中发生异常,则 serviceA 所做的数据库更改不会回滚。

当我使用上面的注释时,上面的示例可以工作但是在 junit 测试中模拟 DAO 调用不起作用。

我的 pom 依赖有错吗?

  1. 为什么这不起作用?
  2. 解决它的方法是什么?

Git Repository Source Code ,在这里你会得到示例 code.It 在编译时给我一些错误。

我认为问题可能是由注释处理顺序引起的。

您可能可以尝试在 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);*/
}

并查看是否还报错

删除所有注释。您需要一些特殊的东西才能使交易正常进行。

问题:

Call goes from serviceA to serviceB. If an exception occurs in serviceB then database changes done by serviceA wont rollback

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 实例。

它需要设置数据源 属性 以便它可以管理连接的事务 通过此数据源。

<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 在您的应用程序中实现 bean 超过 here

一旦你有了交易定义,

您可以通过调用 getTransaction() 方法要求事务管理器使用该定义启动新事务。

然后会 return 一个 TransactionStatus 对象来跟踪交易状态。

如果所有的语句 执行成功,你要求事务管理器通过传递来提交这个事务 在交易状态。

因为 Spring JDBC 模板抛出的所有异常都是子classes 的DataAccessException,你要求事务管理器在捕获到这种异常时回滚事务。

在此class中,您声明了通用类型的事务管理器属性 PlatformTransactionManager

现在你必须注入一个合适的事务管理器 执行。

由于您只处理一个数据源并使用 JDBC 访问它, 你应该选择 DataSourceTransactionManager.

你可以尝试使用 MockitoJUnitRunner.

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(classes = { TestApp.class })
class MyTestClass{ 
..
}

我建议您保持测试简单。您可以从 DI 收益中获利。欲了解更多详情,请访问 Spring documentation:

One of the major advantages of dependency injection is that it should make your code easier to unit test. You can simply instantiate objects using the new operator without even involving Spring. You can also use mock objects instead of real dependencies.

您的测试 class 应该如下所示。

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);
        }
    }
}

您还应该在您的实施中检查空地址列表。测试失败,因为提供商 class 为测试提供了一个没有初始化地址列表的用户实例。

@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;
}