我正在使用TestNG测试业务服务,在春季启动应用程序中使用模拟单元测试。
应用程序是多模块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,在这里您将获得示例代码。编译时会给我一些错误。
答案 0 :(得分:7)
我建议你保持测试简单。您可以从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;
}
答案 1 :(得分:2)
删除所有注释。您需要一些特殊的东西才能使交易有效。
<强>问题:强>
呼叫从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属性,以便它可以管理connectionsmade的事务 通过这个数据源。
<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中,您要求事务管理员在捕获此类异常时回滚事务。
在此类中,您已声明了常规类型的事务管理器属性 的的PlatformTransactionManager 即可。
现在您必须注入适当的事务管理器 实现。
由于您只处理单个数据源并使用JDBC访问它, 你应该选择 DataSourceTransactionManager 。
答案 2 :(得分:1)
我认为这个问题可能是由注释处理顺序引起的。
您可以尝试在方法之前明确设置服务的内部状态,如下所示:
@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);*/
}
并检查错误是否仍然存在