[英]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.