简体   繁体   English

如何测试没有参数的 void 方法?

[英]How to test void method with no parameters?

I have method that is called by another service and it just change one of the field for some rows in database.我有另一个服务调用的方法,它只是更改数据库中某些行的一个字段。 Method looks like this:方法如下所示:

void errorOrders() {
    List<Orders> orders = OrderDAO.getErrorOrders(); //select only fields with status 'error'
    orders.forEach(order -> order.setStatus(OrderStatus.NEW);
    //some logging etc.
}

Is there any way to unit test this method?有没有办法对这个方法进行单元测试? Can I inject myself inside this method and check if orders status was changed?我可以将自己注入此方法并检查订单状态是否已更改?

Cheers!干杯!

I would recommend you refactor your class to make your code testable.我建议您重构您的 class 以使您的代码可测试。 Ideally you would inject the dependency that represents the OrderDAO :理想情况下,您将注入代表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);
    }
}

Then your test code would look like:然后您的测试代码将如下所示:

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

There are ways to mock static methods but I would recommend refactoring to inject the dependencies as it has many other benefits beside testability.有一些方法可以模拟 static 方法,但我建议重构以注入依赖项,因为它除了可测试性之外还有许多其他好处。

If you need to leave the method as static then you can still mock it (in v3.4+ of Mockito):如果您需要将该方法保留为 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 and @khelwood already provided good answers. @ismail 和 @khelwood 已经提供了很好的答案。

  1. If you mock the Object, you can control/see what happens to it如果您模拟 Object,您可以控制/查看它会发生什么
  2. If you change an Object, where you can access the state via public methods, use those如果您更改 Object,您可以通过公共方法访问 state,请使用这些
  3. If you change an Object whose state you cannot access with normal code, use Java Reflections to look at member variables.如果您更改 Object 其 state 您无法使用普通代码访问,请使用 Java 反射查看成员变量。
  4. If you set up Objects, that pass their data to streams and other output, you can put some additional streams etc in between.如果您设置对象,将其数据传递给流和其他 output,您可以在其间放置一些额外的流等。 Use inheritance and reflection if necessary如有必要,使用 inheritance 和反射

Simple example of using Reflection on a shielded class:在屏蔽 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 + "]";
    }

}

Code to access it via reflection in a few ways:通过反射以几种方式访问它的代码:

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

    }

}

For reflections, when I want to access fields, I use my homebrew class that makes it easier to get access to the field and afterwards restore it to normal (last example above uses this):对于反射,当我想访问字段时,我使用我的自制 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);
    }
    
}

The trick with this util class is that when used in a try-resource block, its close() method will get called automatically, whether the block fails or not.这个实用程序 class 的技巧是,当在 try-resource 块中使用时,无论块是否失败,它的close()方法都会自动调用。 It's the same as having the close() or in this case setAccessible(false) call in the finally block, with some extra checks.这与在finally块中调用close()或在本例中为setAccessible(false)调用相同,但需要进行一些额外的检查。

Let the class be:让 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.
    }
}

You need to use assert methods to check end state.您需要使用assert方法检查结束 state。 To test, write something like:要测试,请编写如下内容:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM