ClassCastException при имитации универсального типа с использованием Mockito
У меня есть абстрактный тестовый класс с универсальным параметром.
public abstract class AbstractCarTest<G> {
...
@Mock
protected G carMock;
...
}
Я реализовал конкретный тестовый класс этого.
public class TruckTest extends AbstractCarTest<Truck> {
...
when(truckFactory.getTruck(anyString()).return(carMock);
...
}
Подпись метода выглядит следующим образом
public Truck getTruck(String name);
При беге TruckTest
Я получаю ClassCastException
поговорка
java.lang.ClassCastException: org.mockito.internal.creation.jmock.ClassImposterizer$ClassWithSuperclassToWorkAroundCglibBug$$EnhancerByMockitoWithCGLIB$$... cannot be cast to com.example.Truck
Это почему? Как я могу обойти это?
3 ответа
Mockito генерирует ваш фиктивный класс во время выполнения. Моты создаются путем создания подкласса данного класса и переопределения всех его методов. (Таким образом, ограничение не издеваться final
классы или методы.) Во время выполнения все универсальные типы стираются и заменяются их верхней границей типа в вашем AbstractCarTest
это Object
так как вы не указываете явную верхнюю границу. Поэтому Mockito видит ваш необработанный класс как:
public abstract class AbstractCarTest {
@Mock
protected Object carMock;
}
во время выполнения и создаст макет, который расширяет Object
вместо желаемого Truck
учебный класс. (Вы не можете увидеть это, потому что в cglib есть ошибка, которая не может быть расширена Object
непосредственно. Вместо этого Mockito расширяет внутренний класс, называемый ClassWithSuperclassToWorkAroundCglibBug
.) Обычно компилятор выдаст ошибку типа во время компиляции, когда универсальный тип все еще доступен, но во время выполнения вместо этого возникает загрязнение кучи.
Обходной путь будет следующим:
public abstract class AbstractCarTest<T> {
protected abstract T getCarMock();
// define some base test cases here that use the generic type.
}
public class TruckTest extends AbstractCarTest<Truck> {
@Mock
private Truck carMock;
@Override
protected Truck getCarMock() { return carMock; }
// ...
when(truckFactory.getTruck(anyString()).return(getCarMock());
}
Определяя ваш смоделированный тип без использования обобщений, вы можете получить доступ к макету из абстрактного базового класса с помощью метода получения, где макетированный тип правильно определен как Truck
,
Кажется, что Мокито не будет издеваться над классом, когда он не знает, что это конкретный тип.
Вы можете обойти проблему, выполнив следующие действия:
public abstract class AbstractCarTest<G> {
...
protected G carMock;
...
@Before
public void setUp() throws Exception {
carMock= Mockito.mock(getClazz());
}
protected abstract Class<G> getClazz();
}
public class TruckTest extends AbstractCarTest<Truck> {
...
when(truckFactory.getTruck(anyString()).return(carMock);
...
@Override
protected Class<Truck> getClazz() {
return Truck.class;
}
}
Просто хотел немного улучшить ответ от @geoand.
public abstract class AbstractCarTest<G> {
...
protected G carMock;
...
@Before
public void setUp() throws Exception {
carMock= Mockito.mock(getClazz());
}
private Class<G> getClazz() {
return (Class<G>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
}
public class TruckTest extends AbstractCarTest<Truck> {
...
when(truckFactory.getTruck(anyString()).return(carMock);
...
}
Это в конечном итоге устраняет необходимость в абстрактном методе. Индекс предоставлен getActualTypeArguments()
будет зависеть от того, где аргумент типа появится в вашей декларации. В большинстве случаев это будет 0
Однако, если у вас есть что-то вроде этого:
public abstract class AbstractCarTest<A, B, C, G>
И вы хотели использовать Class
из G
вам нужно будет поставить и индекс 3
,