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

时间:2017-07-13 11:14:31

标签: java mockito

这是班级:

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

我想模拟A类,但将“operands”字段的值设置为空列表。该字段是final我在模拟类时无法在构造函数外修改它。

我尝试使用Reflection,但这与Mockito无关。

需要测试用例的代码:

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

3 个答案:

答案 0 :(得分:0)

好的,所以这里有一些基于你发布的课程的内容(我认为实际的课程有点不同):

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 {}
}

测试类(这两个测试都是绿色):

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

这是有效的,因为测试类和测试类共享相同的包名(因此您可以直接调用new A(...))。

否如果您想要抑制某些A行为,请使用间谍并在需要时使用它来控制行为。

我希望有所帮助。

答案 1 :(得分:0)

也许你可以尝试使用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);

答案 2 :(得分:-1)

封装,你这个笨蛋!!!

封装是隐藏类的实际实现细节。所以在你的情况下,你现在使用myA.operands来访问实际的操作数。这可能会带来一些麻烦。不要公开所有属性,而是将它们private添加,并添加一些访问方法:

public class A {

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

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

不仅你会遵循良好的做法,而且嘲笑它会更容易:

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