我不确定我是否应该干脆不这么做,但我通常习惯于嘲笑我的应用程序的很多领域,当我在实体内部使用Ref和Keys时会遇到问题。
我遵循了文档中的模式,你在实体中使用ref,但你屏蔽了ref,这样你就可以访问它引用的驱动程序对象,如下所示:
@Entity
class Car {
@Id Long id;
@Load Ref<Person> driver; // Person is an @Entity
public Person getDriver() { return driver.get(); }
public void setDriver(Person value) { driver = Ref.create(value); }
}
我的特定场景是使用一个导入程序,在该导入程序中我解析xml并构建我的实体以准备保存它们。由于我的单元测试实际上只是测试xml的导入和解析,所以我模拟了我的dao实现,这样我就不会实际使用数据存储。
然后,当我调用setDriver(它只创建了一个Ref对象)时,我遇到了问题。然后,我使用getDriver方法,该方法将返回null,因为Ref.get直接依赖于数据存储。
有人遇到过这个问题吗?有没有办法创建一个mock Ref对象?我考虑在我的实体中不直接引用Ref,而是引用一个助手类,该类可以提供我在测试中可以控制的Ref?
选项的短列表为:
- 使用PowerMock
- 添加您自己的RefCreator对象
- 使用伪数据存储而不是嘲讽
我从不嘲笑数据存储层。本地单元测试线束是GAE最好的东西之一;它为您提供了一个功能完整的数据存储伪像:
https://cloud.google.com/appengine/docs/java/tools/localunittesting
我将Ref封装在Guava Supplier中,以避免在pojo的单元测试过程中依赖Objectify。供应商以与参考类似的方式转换为数据存储密钥
这个类主要是从Objectify RefTranslatorFactory复制的:
public class RefSupplierTranslatorFactory
extends ValueTranslatorFactory<Supplier<?>, com.google.appengine.api.datastore.Key> {
@SuppressWarnings({ "unchecked", "rawtypes" })
public RefSupplierTranslatorFactory() {
super((Class) Supplier.class);
}
@Override
protected ValueTranslator<Supplier<?>, com.google.appengine.api.datastore.Key> createValueTranslator(
TypeKey<Supplier<?>> tk, CreateContext ctx, Path path) {
final LoadConditions loadConditions = new LoadConditions(tk.getAnnotation(Load.class));
return new ValueTranslator<Supplier<?>, com.google.appengine.api.datastore.Key>(
com.google.appengine.api.datastore.Key.class) {
@Override
protected Supplier<?> loadValue(com.google.appengine.api.datastore.Key value, LoadContext ctx, Path path)
throws SkipException {
Ref<Object> ref = ctx.loadRef(Key.create(value), loadConditions);
return new RefSupplier(ref);
}
@Override
protected com.google.appengine.api.datastore.Key saveValue(Supplier<?> value, boolean index,
SaveContext ctx, Path path) throws SkipException {
return ctx.saveRef(Ref.create(value.get()), loadConditions);
}
};
}
public static class RefSupplier
implements Serializable, Supplier<Object> {
private static final long serialVersionUID = 1L;
final private Ref<?> ref;
public RefSupplier(Ref<?> ref) {
this.ref = ref;
}
@Override
public Object get() {
return ref.get();
}
}
}
假设我有以下Pojos:
@Entity
public static class CarWithSupplier {
@Id
Long id;
Supplier<SteeringWheel> steeringWheel;
List<Supplier<Tire>> tires;
}
@Entity
public static class SteeringWheel {
@Id
Long id;
}
@Entity
public static class Tire {
@Id
Long id;
}
我能够在不依赖Objectify的情况下运行单元测试:
@Test
public void testSupplier() {
CarWithSupplier car = carWithSupplier();
assertNotNull(car.steeringWheel);
assertNotNull(car.tires);
assertEquals(2, car.tires.size());
}
protected CarWithSupplier carWithSupplier() {
CarWithSupplier car = new CarWithSupplier();
car.steeringWheel = Suppliers.ofInstance(steeringWheel());
final Supplier<Tire> leftFrontTire = Suppliers.ofInstance(tire());
final Supplier<Tire> rightFrontTire = Suppliers.ofInstance(tire());
car.tires = ImmutableList.of(leftFrontTire, rightFrontTire);
return car;
}
扩展单元测试,但在测试设置期间设置必要的对象化资源,我能够在数据存储中运行相同的单元测试:
@Before
public void setUpObjectify() throws Exception {
helper.setUp();
closeable = ObjectifyService.begin();
final ObjectifyFactory factory = ObjectifyService.factory();
factory.getTranslators().add(new RefSupplierTranslatorFactory());
factory.register(CarWithSupplier.class);
factory.register(SteeringWheel.class);
factory.register(Tire.class);
}
@Override
protected CarWithSupplier carWithSupplier() {
final CarWithSupplier car = super.carWithSupplier();
final Objectify ofy = ObjectifyService.ofy();
Key<CarWithSupplier> key = ofy.save().entity(car).now();
return ofy.load().key(key).now();
}
@Override
protected Tire tire() {
final Tire tire = super.tire();
ObjectifyService.ofy().save().entity(tire).now();
return tire;
}
@Override
protected SteeringWheel steeringWheel() {
final SteeringWheel steeringWheel = super.steeringWheel();
ObjectifyService.ofy().save().entity(steeringWheel).now();
return steeringWheel;
}
我的pojos的单元测试很有价值,因为它们最初是使用来自第三方web API服务的JSON响应(使用Gson)填充的。我发现将Gson解析的测试与对象化数据存储函数的测试分开是很有价值的。我后来在集成测试中对它们进行了全面测试。
我还没有广泛使用它,所以如果这可能会导致问题或以其他方式剥夺直接使用Ref的优势,我欢迎@stickpfigure提供意见。
我在这里编写了模拟Objectify的Key
和Ref
类的代码。
使用:
Ref<MyEntity> ref = MockObjectify.ref(myEntity);
来源:
package present.objectify;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.apphosting.api.ApiProxy;
import com.google.common.cache.LoadingCache;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.impl.KeyMetadata;
import com.googlecode.objectify.impl.Path;
import com.googlecode.objectify.impl.translate.CreateContext;
import java.util.Collections;
import java.util.Map;
import java.util.function.Supplier;
import present.engine.Caches;
/**
* Creates Objectify mocks.
*
* @author Bob Lee
*/
public class MockObjectify {
/** Creates a reference to the given instance. */
public static <T> Ref<T> ref(T instance) {
return new Ref<T>() {
@Override public T get() {
return instance;
}
@Override public boolean isLoaded() {
return true;
}
@Override public Key<T> key() {
return MockObjectify.key(instance);
}
};
}
/** Creates a key with a mock application ID. */
public static <T> Key<T> key(T instance) {
@SuppressWarnings("unchecked")
KeyMetadata<T> metadata = (KeyMetadata<T>) keyMetadatas.getUnchecked(instance.getClass());
return inMockEnvironment(() -> Key.create(metadata.getRawKey(instance)));
}
/** Creates a key with a mock application ID. */
public static <T> Key<T> key(Class<? extends T> kindClass, long id) {
KeyMetadata<T> metadata = keyMetadata(kindClass);
return inMockEnvironment(() -> Key.create(KeyFactory.createKey(metadata.getKind(), id)));
}
/** Creates a key with a mock application ID. */
public static <T> Key<T> key(Class<? extends T> kindClass, String name) {
KeyMetadata<T> metadata = keyMetadata(kindClass);
return inMockEnvironment(() -> Key.create(KeyFactory.createKey(metadata.getKind(), name)));
}
/** Creates a key with a mock application ID. */
public static <T> Key<T> key(Key<?> parent, Class<? extends T> kindClass, long id) {
KeyMetadata<T> metadata = keyMetadata(kindClass);
return inMockEnvironment(() -> Key.create(KeyFactory.createKey(parent.getRaw(), metadata.getKind(), id)));
}
/** Creates a key with a mock application ID. */
public static <T> Key<T> key(Key<?> parent, Class<? extends T> kindClass, String name) {
KeyMetadata<T> metadata = keyMetadata(kindClass);
return inMockEnvironment(() -> Key.create(KeyFactory.createKey(parent.getRaw(), metadata.getKind(), name)));
}
private static final ObjectifyFactory factory = new ObjectifyFactory();
private static final LoadingCache<Class<?>, KeyMetadata<?>> keyMetadatas = Caches.create(
type -> new KeyMetadata<>(type, new CreateContext(factory), Path.root()));
@SuppressWarnings("unchecked")
private static <T> KeyMetadata<T> keyMetadata(Class<? extends T> clazz) {
return (KeyMetadata<T>) keyMetadatas.getUnchecked(clazz);
}
private static <T> T inMockEnvironment(Supplier<T> supplier) {
ApiProxy.Environment original = ApiProxy.getCurrentEnvironment();
try {
ApiProxy.setEnvironmentForCurrentThread(mockEnvironment);
return supplier.get();
} finally {
ApiProxy.setEnvironmentForCurrentThread(original);
}
}
private static final ApiProxy.Environment mockEnvironment = new ApiProxy.Environment() {
@Override public String getAppId() {
return "mock";
}
@Override public String getModuleId() {
throw new UnsupportedOperationException();
}
@Override public String getVersionId() {
throw new UnsupportedOperationException();
}
@Override public String getEmail() {
throw new UnsupportedOperationException();
}
@Override public boolean isLoggedIn() {
throw new UnsupportedOperationException();
}
@Override public boolean isAdmin() {
throw new UnsupportedOperationException();
}
@Override public String getAuthDomain() {
throw new UnsupportedOperationException();
}
@Override public String getRequestNamespace() {
throw new UnsupportedOperationException();
}
@Override public Map<String, Object> getAttributes() {
return Collections.emptyMap();
}
@Override public long getRemainingMillis() {
throw new UnsupportedOperationException();
}
};
}