在EpicPandaForce's answer 的启发和一些研究(见this article)之后,我找到了一个我满意的解决方案。
我决定从我的项目中删除 Dagger 2,因为我对它进行了过度设计。我的应用程序依赖于一个存储库类,现在是一个 ViewModelProvider.Factory 实现,一旦应用程序运行,它们都是必需的。我对 Dagger 的了解已经足够让我自己满意了,所以我很乐意将它排除在这个特定的项目之外,并在 Application 类中创建两个依赖项。这些类如下所示:
我的应用程序类,它创建了我的ViewModel 工厂,为它提供了它的存储库,并向我的活动公开了一个getViewModelFactory() 方法:
public class JourneyStoreApplication extends Application {
private final JourneyStoreViewModelFactory journeyStoreViewModelFactory;
{
// Instantiate my viewmodel factory with my repo here
final JourneyRepository journeyRepository = new JourneyRepositoryImpl();
journeyStoreViewModelFactory = new JourneyStoreViewModelFactory(journeyRepository);
}
@Override
public void onCreate() {
super.onCreate();
}
public JourneyStoreViewModelFactory getViewModelFactory(){
return journeyStoreViewModelFactory;
}
}
我的ViewModel 工厂,它使用存储库引用创建新的ViewModels。随着我添加更多Activity 类和ViewModels,我将对此进行扩展:
public class JourneyStoreViewModelFactory implements ViewModelProvider.Factory {
private final JourneyRepository journeyRepository;
JourneyStoreViewModelFactory(JourneyRepository journeyRepository){
this.journeyRepository = journeyRepository;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if(modelClass == AddJourneyViewModel.class){
// Instantiates the ViewModels with their repository reference.
return (T) new AddJourneyViewModelImpl(journeyRepository);
}
throw new IllegalArgumentException(String.format("Requested class %s did not match expected class %s.", modelClass, AddJourneyViewModel.class));
}
}
我的AddJourneyActivity 类,它使用AddJourneyViewModel:
public class AddJourneyActivity extends AppCompatActivity {
private static final String TAG = AddJourneyActivity.class.getSimpleName();
private AddJourneyViewModel addJourneyViewModel;
private EditText departureTextField;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_journey);
JourneyStoreApplication app = (JourneyStoreApplication) getApplication();
addJourneyViewModel = ViewModelProviders
// Gets the ViewModelFactory instance and creates the ViewModel.
.of(this, app.getViewModelFactory())
.get(AddJourneyViewModel.class);
departureTextField = findViewById(R.id.addjourney_departure_addr_txt);
}
//...
}
但这仍然留下了测试的问题,这是我的主要问题之一。
旁注:我将所有ViewModel 类抽象化(仅使用方法),然后为我的真实应用程序和测试代码实现它们。这是因为我发现这比直接ViewModels 更容易extends,然后尝试覆盖他们的方法并隐藏他们的状态以创建一个假版本。
无论如何,我扩展了我的 JourneyStoreApplication 课程(我知道这与我自己相矛盾,但它是一个小课程,因此很容易管理)并用它来创建一个提供我的假 ViewModels 的地方:
public class FakeJourneyStoreApplication extends JourneyStoreApplication {
private final JourneyStoreViewModelFactory fakeJourneyStoreViewModelFactory;
{ // Create my fake instances here for my tests
final JourneyRepository fakeJourneyRepository = new FakeJourneyRepositoryImpl();
fakeJourneyStoreViewModelFactory = new FakeJourneyStoreViewModelFactory(fakeJourneyRepository);
}
@Override
public void onCreate() {
super.onCreate();
}
public JourneyStoreViewModelFactory getViewModelFactory(){
return fakeJourneyStoreViewModelFactory;
}
}
我伪造了我的ViewModels 并从FakeJourneyStoreViewModelFactory 返回了它们的实例。稍后我可能会简化这一点,因为“假”样板可能比需要的要多。
离开 this guide(第 4.9 节),我扩展了 AndroidJUnitRunner 以提供我的 fake Application 到我的测试:
public class CustomTestRunner extends AndroidJUnitRunner {
@Override
public Application newApplication(ClassLoader cl, String className, Context context)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return super.newApplication(cl, FakeJourneyStoreApplication.class.getName(), context);
}
}
最后,我将自定义测试运行器添加到我的 build.gradle 文件中:
android {
defaultConfig {
// Espresso
testInstrumentationRunner "com.<my_package>.journeystore.CustomTestRunner"
}
}
我将把这个问题再开放 24 小时,以防有人要添加有用的东西,然后我会选择这个作为答案。