[英]How to test void method with no parameters?
我有另一个服务调用的方法,它只是更改数据库中某些行的一个字段。 方法如下所示:
void errorOrders() {
List<Orders> orders = OrderDAO.getErrorOrders(); //select only fields with status 'error'
orders.forEach(order -> order.setStatus(OrderStatus.NEW);
//some logging etc.
}
有没有办法对这个方法进行单元测试? 我可以将自己注入此方法并检查订单状态是否已更改?
干杯!
我建议您重构您的 class 以使您的代码可测试。 理想情况下,您将注入代表OrderDAO
的依赖项:
class ErrorChecker {
private final OrderDAO orderDAO;
public ErrorChecker(OrderDAO orderDAO) {
this.orderDAO = orderDAO;
}
public void errorOrders() {
List<Orders> orders = orderDAO.getErrorOrders();
orders.forEach(order -> order.setStatus(OrderStatus.NEW);
}
}
然后您的测试代码将如下所示:
@Test
void testErrorOrders() {
Order order1 = mock(Order.class);
Order order2 = mock(Order.class);
OrderDAO orderDAO = mock(OrderDAO.class);
when(orderDAO.getErrorOrders()).thenReturn(List.of(order1, order2));
ErrorChecker errorChecker = new ErrorChecker(orderDAO);
errorChecker.errorOrders();
verify(order1).setState(OrderStatus.NEW);
verify(order2).setState(OrderStatus.NEW);
}
有一些方法可以模拟 static 方法,但我建议重构以注入依赖项,因为它除了可测试性之外还有许多其他好处。
如果您需要将该方法保留为 static 那么您仍然可以模拟它(在 Mockito 的 v3.4+ 中):
@Test
void testErrorOrders() {
try (MockedStatic mocked = mockStatic(OrderDAO.class)) {
mocked.when(OrderDAO.getErrorOrders()).thenReturn(List.of(order1, order2));
ErrorChecker errorChecker = new ErrorChecker(orderDAO);
errorChecker.errorOrders();
mocked.verify(order1).setState(OrderStatus.NEW);
}
}
@ismail 和 @khelwood 已经提供了很好的答案。
在屏蔽 class 上使用反射的简单示例:
package stackoverflow.simplefieldaccess;
public class ShieldedClass {
private int mStatus;
public ShieldedClass() {
mStatus = 666;
}
public void setStatus(final int pStatus) {
mStatus = pStatus; // usually with ints be careful and do checks here, but for the sake of simplicity we leave that out
}
@Override public String toString() {
return getClass().getSimpleName() + "[status:" + mStatus + "]";
}
}
通过反射以几种方式访问它的代码:
package stackoverflow.simplefieldaccess;
import java.lang.reflect.Field;
import jc.lib.lang.reflect.JcFieldAccess;
public class SimpleFieldAccess {
public static void main(final String[] args) throws NoSuchFieldException, SecurityException {
final ShieldedClass so = new ShieldedClass();
System.out.println("Object.status before change: " + so);
so.setStatus(667);
System.out.println("Object.status after change: " + so);
System.out.println();
System.out.println("Accessing Object.status via Reflection...");
final Class<? extends ShieldedClass> cls = so.getClass();
final Field fieldToChance = cls.getDeclaredField("mStatus");
{
System.out.println("\nBad read access");
try { // will result in java.lang.IllegalAccessException
System.out.println("\tReading Object.status fiels via Reflection: " + fieldToChance.getInt(so));
throw new IllegalStateException("UNEXOECTED ERROR!");
} catch (final java.lang.IllegalAccessException e) {
System.out.println("\tAs expected: IllegalAccessException");
}
}
{
System.out.println("\nBad write access");
try { // will result in java.lang.IllegalAccessException
fieldToChance.set(so, Integer.valueOf(1337));
System.out.println("\tObject.status after change: " + so);
} catch (final java.lang.IllegalAccessException e) {
System.out.println("\tAs expected: IllegalAccessException");
}
}
{
System.out.println("\nGood manual read and write access");
final boolean isFieldOriginallyAccessible = fieldToChance.isAccessible();
try { // will result in java.lang.IllegalAccessException
if (!isFieldOriginallyAccessible) fieldToChance.setAccessible(true);
System.out.println("\tReading Object.status field via Reflection: " + fieldToChance.getInt(so));
fieldToChance.set(so, Integer.valueOf(4321));
System.out.println("\tObject.status after change: " + so);
} catch (final java.lang.IllegalAccessException e) {
e.printStackTrace();
} finally {
if (!isFieldOriginallyAccessible) fieldToChance.setAccessible(false);
}
}
{
System.out.println("\nGood automated read and write access");
try (JcFieldAccess fa = new JcFieldAccess(fieldToChance)) { // will result in java.lang.IllegalAccessException
System.out.println("\tReading Object.status field via Reflection: " + fieldToChance.getInt(so));
fieldToChance.set(so, Integer.valueOf(123));
System.out.println("\tObject.status after change: " + so);
} catch (final java.lang.IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
对于反射,当我想访问字段时,我使用我的自制 class 可以更轻松地访问该字段,然后将其恢复正常(上面的最后一个示例使用这个):
package jc.lib.lang.reflect;
import java.io.Closeable;
import java.lang.reflect.AccessibleObject;
public class JcFieldAccess implements Closeable {
private final AccessibleObject mField;
private final boolean mIsAccessible;
public JcFieldAccess(final AccessibleObject pField) {
mField = pField;
mIsAccessible = mField.isAccessible();
if (!mIsAccessible) mField.setAccessible(true);
}
@Override public void close() {
if (mIsAccessible) return;
if (mField != null) mField.setAccessible(false);
}
}
这个实用程序 class 的技巧是,当在 try-resource 块中使用时,无论块是否失败,它的close()
方法都会自动调用。 这与在finally
块中调用close()
或在本例中为setAccessible(false)
调用相同,但需要进行一些额外的检查。
让 class 为:
class HandleErrorOrders {
private OrderDAO orderDAO;
HandleErrorOrders(final OrderDAO orderDAO) {
this.orderDAO = orderDAO;
}
public void errorOrders() {
List<Orders> orders = OrderDAO.getErrorOrders(); //select only fields with status 'error'
orders.forEach(order -> order.setStatus(OrderStatus.NEW);
//some logging etc.
}
}
您需要使用assert
方法检查结束 state。 要测试,请编写如下内容:
class HandleErrorOrdersTest {
@Mock
private OrderDAO orderDAO;
@InjectMocks
private HandleErrorOrders handleErrorOrders;
@Test
void testErrorOrders() {
Order order1 = mock(Order.class);
Order order2 = mock(Order.class);
when(orderDAO.getErrorOrders()).thenReturn(List.of(order1, order2));
ErrorChecker errorChecker = new ErrorChecker(orderDAO);
errorChecker.errorOrders();
//asset checks
Assert.assertEquals(OrderStatus.NEW, order1.getStatus());
Assert.assertEquals(OrderStatus.NEW, order2.getStatus());
//verification checks
Mockito.verify(orderDAO).getErrorOrders();
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.