[英]How to unit test an abstract class method that depends on equals() with Mockito?
I need to test an abstract class in Java, and I have a method which depends on equals()
to provide a result. 我需要在Java中测试一个抽象类,并且我有一个依赖于
equals()
来提供结果的方法。
public abstract class BaseClass<T extends BaseClass<T>> {
public boolean isCanonical() {
return toCanonical() == this;
}
public T toCanonical() {
T asCanonical = asCanonical();
if (equals(asCanonical)) {
//noinspection unchecked
return (T) this;
}
return asCanonical;
}
protected abstract T asCanonical();
}
As you can see the toCanonical()
method compare itself with asCanonical
build calling the abstract method asCanonical()
. 正如您所看到的,
toCanonical()
方法将自身与asCanonical
构建进行比较,调用抽象方法asCanonical()
。
To test this I need to create an empty implementation of my abstract class and make mockito calls real methods for the two implemented method and return my own classes for the asCanonical(). 为了测试这个,我需要创建一个抽象类的空实现,并使mockito为两个实现的方法调用实际方法,并为asCanonical()返回我自己的类。
BaseClass aCanonicalClass;
BaseClass aNonCanonicalClass;
@Mock
BaseClass sut;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
// these are the methods under test, use the real ones
Mockito.when(sut.isCanonical())
.thenCallRealMethod();
Mockito.when(sut.toCanonical())
.thenCallRealMethod();
}
Normally if this wasn't the equals()
method I would just do this: 通常,如果这不是
equals()
方法,我会这样做:
@Test
public void test_isCanonical_bad() {
// test true
Mockito.when(sut.equals(aCanonicalClass))
.thenReturn(true);
Mockito.when(sut.equals(aNonCanonicalClass))
.thenReturn(false);
Mockito.when(sut.asCanonical())
.thenReturn(aCanonicalClass);
Assert.assertTrue(sut.isCanonical());
// test false
Mockito.when(sut.equals(aNonCanonicalClass))
.thenReturn(true);
Mockito.when(sut.equals(aCanonicalClass))
.thenReturn(false);
Mockito.when(sut.asCanonical())
.thenReturn(aCanonicalClass);
Assert.assertFalse(sut.isCanonical());
}
Which give this error: 哪个给出了这个错误:
org.mockito.exceptions.misusing.MissingMethodInvocationException: when() requires an argument which has to be 'a method call on a mock'. For example: when(mock.getArticles()).thenReturn(articles); Also, this error might show up because: 1. you stub either of: final/private/equals()/hashCode() methods. Those methods *cannot* be stubbed/verified. Mocking methods declared on non-public parent classes is not supported. 2. inside when() you don't call method on mock but on some other object.
One of Mockito limitations is that it doesn't allow to mock the equals()
and hashcode()
methods. Mockito的一个限制是它不允许模拟
equals()
和hashcode()
方法。
As a workaround I just pass itself when I want to test the condition equals() = true
and another random object when I want to test the condition equals() = false
. 作为一种解决办法时,我想测试的条件我只是通过自己
equals() = true
当我想要测试的条件和其他随机对象equals() = false
。
@Test
public void test_isCanonical() {
// test true
Mockito.when(sut.asCanonical())
.thenReturn(sut);
Assert.assertTrue(sut.isCanonical());
// test false
Mockito.when(sut.asCanonical())
.thenReturn(aCanonicalClass);
Assert.assertFalse(sut.isCanonical());
}
But this test pass even if I change the implementation to this: 但即使我将实现更改为此,此测试也会通过:
public T toCanonical() {
T asCanonical = asCanonical();
if (this == asCanonical) {
//noinspection unchecked
return (T) this;
}
return asCanonical;
}
or even this: 甚至这个:
public T toCanonical() {
return asCanonical();
}
which is wrong! 这是错的! Comparison should be done with equals.
应该用equals进行比较。 Doing so by reference is not the same thing.
通过引用这样做是不一样的。
Things gets impossible to test when I get to the toCanonical()
method: 当我到达
toCanonical()
方法时,事情变得无法测试:
@Test
public void test_toCanonical_bad() {
// test canonical
Mockito.when(sut.equals(aCanonicalClass))
.thenReturn(true);
Mockito.when(sut.equals(aNonCanonicalClass))
.thenReturn(false);
Mockito.when(sut.asCanonical())
.thenReturn(aCanonicalClass);
Assert.assertSame(sut, sut.toCanonical());
// test non-canonical
Mockito.when(sut.equals(aNonCanonicalClass))
.thenReturn(true);
Mockito.when(sut.equals(aCanonicalClass))
.thenReturn(false);
Mockito.when(sut.asCanonical())
.thenReturn(aCanonicalClass);
Assert.assertNotEquals(sut, sut.toCanonical());
Assert.assertSame(aCanonicalClass, sut.toCanonical());
}
This of course doesn't work, and doesn't really makes sense to apply the same workaround because I would just test that the return value of the function is the one of asCanonical()
: 这当然不起作用,并且应用相同的解决方法并没有多大意义,因为我只测试函数的返回值是
asCanonical()
的返回值:
@Test
public void test_toCanonical() {
// test canonical
Mockito.when(sut.asCanonical())
.thenReturn(sut);
Assert.assertSame(sut, sut.toCanonical());
// test non-canonical
Mockito.when(sut.asCanonical())
.thenReturn(aCanonicalClass);
Assert.assertNotEquals(sut, sut.toCanonical());
Assert.assertSame(aCanonicalClass, sut.toCanonical());
}
Both test are pointless because of the fact I can't mock the result of equals()
. 这两个测试都没有意义,因为我无法模拟
equals()
的结果。
Is there a way to test at least equals()
is being called with the object I gave it? 有没有办法测试至少
equals()
是用我给它的对象调用的? (spy it) (间谍)
Is there any other way to test this? 有没有其他方法来测试这个?
EDIT : this is a semplification of the actual code I'm working with.
编辑 :这是我正在使用的实际代码的简化。
I modified the example code to at least show the
toCanonical()
andasCanonical()
methods are supposed to return instances of the same class (and not just any class)我修改了示例代码以至少显示
toCanonical()
和asCanonical()
方法应该返回同一个类的实例(而不仅仅是任何类)I want to test the base class here.
我想在这里测试基类。 Only the concrete implementation know how to build an actual canonical class (
asCanonical()
) or check if this class is equal to a canonical class (equals()
).只有具体的实现知道如何构建一个实际的规范类(
asCanonical()
)或检查这个类是否等于规范类(equals()
)。 And please note thattoCanonical()
return ITSELF if it is equals to the canonical version of itself.请注意,
toCanonical()
返回toCanonical()
如果它等于自身的规范版本。 Againequals != same
.再
equals != same
。The actual implementations are immutable classes.
实际的实现是不可变的类。 And since they are many and this code is the same for everyone i put it in a base class.
因为它们很多,而且这个代码对于我把它放在基类中的每个人都是一样的。
Why would you test an abstract class? 你为什么要测试一个抽象类? If you really want to test the
toCanonical
method (which is not final
, so you can override it later) you can instantiate a temp concrete class and test it. 如果你真的想测试
toCanonical
方法(这不是final
,所以你可以稍后覆盖它),你可以实例化一个临时具体类并测试它。 That is way easier then using Mockito and other stuff. 这比使用Mockito和其他东西更容易。
Ie: 即:
@Test
public void myAwesomeTest() {
// Arrange
BaseClass test = new BaseClass<String>() {
// provide concrete implementation
protected String asCanonical() {
return "Foo";
}
};
// Act
String result = test.toCanonical("Bar");
// Assert
}
Once the simple case works, you can extract the creation of baseclass
into a factory method ie MakeBaseClass(String valueToReturnForAsCanonical
and write more tests. 一旦简单的情况工作,您可以将
baseclass
的创建提取到工厂方法,即MakeBaseClass(String valueToReturnForAsCanonical
并编写更多测试。
Using Spy's are handy if you are in some regions of untested code and you have no way to stub out parts or create some seams in the code. 如果您在某些未经测试的代码区域中使用Spy是很方便的,并且您无法在代码中存根零件或创建一些接缝。 In this case I think you can suffice with writing the boilerplate yourself instead of relying on some magic by Mockito.
在这种情况下,我认为你可以自己编写样板,而不是依靠Mockito的魔法。
Good luck! 祝好运!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.