简体   繁体   English

Mockito:模拟一个类,但在构造函数受保护时设置final字段的值

[英]Mockito: Mock a class but set the value of the final field when the constructor is protected

Here's the class: 这是课程:

public class A {

   public final SqlOperator op;
   public final ImmutableList<RexNode> operands;
   public final RelDataType type;

   protected A(RelDataType type,SqlOperator op,List<? extends RexNode> operands) {
     assert type != null : "precondition: type != null";
     assert op != null : "precondition: op != null";
     assert operands != null : "precondition: operands != null";
     this.type = type;
     this.op = op;
     this.operands = ImmutableList.copyOf(operands);
     assert op.getKind() != null : op;
     assert op.validRexOperands(operands.size(), Litmus.THROW) : this;
}

I want to mock class A but set the value of "operands" field to an empty list. 我想模拟A类,但将“操作数”字段的值设置为空列表。 The field being final I cannot modify it outside the constructor when I mock the class. 当我模拟该类时,该字段为final,我无法在构造函数之外对其进行修改。

I tried to use Reflection but that didn't work with Mockito. 我尝试使用Reflection,但不适用于Mockito。

The code for which test case is needed: 需要测试用例的代码:

public static String extractGranularity(A call) {
    if (call.getKind() != SqlKind.FLOOR || call.getOperands().size() != 2) {
        return null;
    }
    final B flag = (B) call.operands.get(1);   // This is the problem area
    final TimeUnitRange timeUnit = (TimeUnitRange) flag.getValue();
    if (timeUnit == null) {
        return null;
    }
    return timeUnitSwitch(timeUnit);
}

OK so here's something based on the classes you did post (I assume the real ones are a little bit different): 好的,所以这是基于您发布的类的内容(我认为真实的类有所不同):

package so45078998;

import com.google.common.collect.ImmutableList;
import java.util.List;

public class A {

    public final SqlOperator op;
    public final ImmutableList<RexNode> operands;
    public final RelDataType type;

    protected A(RelDataType type, SqlOperator op, List<? extends RexNode> operands) {
        assert type != null : "precondition: type != null";
        assert op != null : "precondition: op != null";
        assert operands != null : "precondition: operands != null";
        this.type = type;
        this.op = op;
        this.operands = ImmutableList.copyOf(operands);
        assert op.getKind() != null : op;
        assert op.validRexOperands(operands.size(), Litmus.THROW) : this;
    }

    public Object getKind() {
        return op.getKind();
    }

    public ImmutableList<RexNode> getOperands() {
        return operands;
    }

    static class SqlOperator{

        private Object kind;

        public Object getKind() {
            return kind;
        }

        void setKind(final Object kind) {
            this.kind = kind;
        }

        boolean validRexOperands(int size, Litmus litmus) {
            return true;
        }
    }

    static class RexNode{
        private Object value;

        public Object getValue() {
            return value;
        }

        void setValue(final Object value) {
            this.value = value;
        }
    }
    static class RelDataType{}
    static enum Litmus{ THROW }
}

// ------------------------------------------------------

package so45078998;

public class ToTest {
    public static String extractGranularity(A call) {
        if (call.getKind() != SqlKind.FLOOR || call.getOperands().size() != 2) {
            return null;
        }
        final B flag = (B) call.operands.get(1);   // This is the problem area
        final TimeUnitRange timeUnit = (TimeUnitRange) flag.getValue();
        if (timeUnit == null) {
            return null;
        }
        return timeUnitSwitch(timeUnit);
    }

    private static String timeUnitSwitch(final TimeUnitRange timeUnit) {
        return "OK";
    }

    enum SqlKind { FLOOR, OTHER }
    static class TimeUnitRange {}
    static class B extends A.RexNode {}
}

And the test class (both tests are green here): 和测试类(两个测试在这里都是绿色的 ):

package so45078998;

import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;

public class ToTestTest {
    @Test
    public void testExtractGranularityWithKindNotFloor() throws Exception {
        ToTest.B rexNode1 = new ToTest.B();
        ToTest.B rexNode2 = new ToTest.B();
        List<A.RexNode> operands = new ArrayList<>();
        operands.add(rexNode1);
        operands.add(rexNode2);
        final A.RelDataType relDataType = new A.RelDataType();
        final A.SqlOperator sqlOperator = new A.SqlOperator();
        sqlOperator.setKind(ToTest.SqlKind.OTHER);
        A a = new A(relDataType, sqlOperator, operands);

        Assert.assertNull(ToTest.extractGranularity(a));
    }

    @Test
    public void testExtractGranularity() throws Exception {
        ToTest.B rexNode1 = new ToTest.B();
        ToTest.B rexNode2 = new ToTest.B();
        rexNode2.setValue(new ToTest.TimeUnitRange());
        List<A.RexNode> operands = new ArrayList<>();
        operands.add(rexNode1);
        operands.add(rexNode2);
        final A.RelDataType relDataType = new A.RelDataType();
        final A.SqlOperator sqlOperator = new A.SqlOperator();
        sqlOperator.setKind(ToTest.SqlKind.FLOOR);
        A a = new A(relDataType, sqlOperator, operands);

        Assert.assertEquals("OK", ToTest.extractGranularity(a));
    }
}

This works because the test class and the tested class share the same package name (and so you can call directly new A(...) ). 这是可行的,因为测试类和被测试类共享相同的包名称(因此您可以直接调用new A(...) )。

No if you want to suppress some A behavior, use a spy and use it to control the behavior when required. 否,如果您想抑制某些A行为,请使用间谍并在需要时使用它来控制行为。

I hope that helps. 希望对您有所帮助。

Maybe you can try hacking with Unsafe: 也许您可以尝试使用Unsafe进行黑客入侵:

sun.misc.Unsafe unsafe = null;
try {
  final PrivilegedExceptionAction<sun.misc.Unsafe> action = () -> {
    Field unsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
    unsafe.setAccessible(true);
    return (sun.misc.Unsafe) unsafe.get(null);
  };
  unsafe = AccessController.doPrivileged(action);
} catch (Exception e) {
  throw new RuntimeException("Unable to load unsafe", e);
}
long operandsOffset = unsafe.objectFieldOffset(
                                A.class.getDeclaredField("operands"));
A instance = // your instance of A
ImmutableList<RexNode> yourOverridingList = ...
unsafe.getAndSetObject(instance, operandsOffset, yourOverridingList);

Encapsulate , you fool!!! 封装 ,你这个傻瓜!!!

Encapsulating is hiding the actual implementation details of a class. 封装隐藏了类的实际实现细节。 So in your case right now you use myA.operands to access the actual operands. 因此,根据您的情况,现在您可以使用myA.operands访问实际的操作数。 And that could potentially bring some troubles. 这可能会带来一些麻烦。 Instead of having all your attributes public make them private and add some accessor methods: 不必公开所有属性,而是将它们private并添加一些访问器方法:

public class A {

   private final ImmutableList<RexNode> operands;
   // rest of attributes and constructor...

   public ImmutableList<RexNode> getOperands() {
       return operands;
   }
}

Not only you'll be following a good practice but also mocking it will be easier: 不仅您会遵循良好的做法,而且嘲笑它也会更容易:

A myA = mock(A.class);
Mockito.when(myA.getOperands())
    .thenReturn(/* what you want it to return */);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 模拟决赛 class 和 Mockito 2 - Mock final class with Mockito 2 Mockito-使用@Spy和@InjectMocks时如何注入构造函数标记为@Mock的最终字段 - Mockito - how to inject final field marked as @Mock by constructor when using @Spy and @InjectMocks 如何用 mockito 模拟最后一堂课 - How to mock a final class with mockito 我该如何模拟通过Powermock和Mockito通过私有构造函数初始化的私有static final字段? - How do I mock a private static final field initialized via a private constructor using Powermock and Mockito? 无法使用 Mockito 2 模拟最终的 Kotlin 类 - Cannot mock final Kotlin class using Mockito 2 带PowerMock和Mockito的受保护构造函数的测试类 - Testing class with Protected Constructor w/ PowerMock and Mockito 为什么在Mockito测试期间源代码另有说明时,受保护的最终字段为空? - Why protected final field is null when source code says otherwise during Mockito test? 使用 mockito 或 Jmockit 模拟私有静态最终字段 - Mock private static final field using mockito or Jmockit 当超类保护了 final 字段时,我可以创建一个无参数构造函数吗 - Could I create a no parameter constructor when superclass has protected final field Mockito:将类模拟注入私有接口字段 - Mockito: inject a class mock into a private interface field
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM