Android 为 Espresso 测试模拟 Dagger2 注入依赖项
Android Mocking a Dagger2 injected dependency for a Espresso test
我有一个高度依赖注入的 (dagger2) 应用程序。我想 运行 一个 espresso 测试 而不让测试浏览整个应用程序,然后登录到应用程序。
我想开始我的 teleActivity,并模拟登录管理器。但是在任何@test 函数中,我们已经在调用 onCreate 时命中了空指针。如果我在启动 activity(如下所示)之前覆盖它,则 activity 为空。
据我了解,切换下划线依赖项的能力是我们使用 Dagger2 的一个重要原因,否则它只是一个过度设计的单例。我如何覆盖、模拟或将注入切换到测试匕首模块——这样我就可以创建这个简单的浓缩咖啡测试。
请注意,如果有区别的话,我也在 MVP 设计模式中写下了所有这些内容。
TeleActivity
@Inject
TelePresenter mTelePresenter;
@Inject
public LoginStateManager mLoginStateManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ButterKnife.bind(this);
DaggerInjectorTele.get().inject(this);
mTelePresenter.setTeleDependencies(this);
Intent intent = getIntent();
String searchId = null;
if (intent != null) {
searchId = intent.getStringExtra(Constants.SEARCH_ID);
}
mTelePresenter.onCreateEvent(searchId,
Helper.makeAuthorizationHeader(
// CRASH Null pointer
mLoginStateManager.getBaseLoginResponse().getAccessToken()));
}
浓缩咖啡
@LargeTest
@RunWith(AndroidJUnit4.class)
public class TeleTest {
@Rule
public ActivityTestRule<TeleActivity> mActivityTestRule = new ActivityTestRule(
TeleActivity.class) {
@Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
TeleActivity teleActivity = (TeleActivity)getActivity();
//teleActivity NULL!
teleActivity.mLoginStateManager = mock(LoginStateManager.class);
LoginResponse loginResponse = mock(LoginResponse.class);
when(loginResponse.getAccessToken()).thenReturn("1234");
// Nope here still null
when(teleActivity.mLoginStateManager.getBaseLoginResponse()).thenReturn(loginResponse);
}
};
匕首注射器
public class DaggerInjectorTele {
private static TelePresenterComponent telePresenterComponent =
DaggerTelePresenterComponent.builder().build();
public static TelePresenterComponent get() {
return telePresenterComponent;
}
}
TelePresenterComponent
@Singleton
@Component(modules = {TelePresenterModule.class,
LoginStateManagerModule.class})
public interface TelePresenterComponent {
void inject(TeleActivity activity);
}
TelePresenterModule
@Module
public class TelePresenterModule {
@Provides
@Singleton
public TelePresenter getTelePresenter() {
return new TelePresenter();
}
}
LoginStateManagerModule
@Module
public class LoginStateManagerModule {
@Provides
@Singleton
public LoginStateManager getLoginStateManager(){
return new LoginStateManager();
}
}
似乎是架构问题而不是小问题。
首先,我不会创建静态 class 来调用 dagger2 组件,我的方法会更 android 为中心,我的意思是使用单例应用程序及其所有功能。
总之...
在没有 运行 整个工作流程的情况下 运行 测试的最佳方法是将您的项目分成两个不同的项目:
1- UI 应用程序、您的 android 活动和片段等...
使用企业架构的 2-Logic 模块说 MVP/MVC/MVVM(它应该是您 android 工作室中的另一个项目)
匕首应该用在什么地方?在您的 UI 应用程序中,用于将逻辑模块粘贴到您的 UI.
如何测试应用程序的不同部分(逻辑模块)?因为你将你的逻辑分成不同的部分,为它们编写测试会容易得多,即使你不再需要 Esperesso。使用 JUnit 和 Mockito 进行简单的单元测试可以帮助您,而无需 运行 整个工作流程。
请注意,您的 UI 应用中不应包含任何类型的逻辑。
我的观点是干净的架构:https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
我在 github 中为上述方法提供了一个简单的脚手架,您也可以阅读它,如果您喜欢的话:
https://github.com/vahidhashemi/android_clean_architecture
中没有设置值
LoginStateManager
因此,当您构建组件时,您会同时获得 TelePresenter 依赖项和 LoginStateManager 依赖项
但是两者的成员变量都没有设置值。所以我认为你需要在访问它们之前设置成员变量的值。
getBaseLoginResponse().getAccessToken())
上面的代码行给你 null 因为你没有设置值。所以在访问它之前你需要先设置值
首先,您决定使用依赖注入 (Dagger2) 是一个非常好的决定,确实会让您的测试更容易编写。
您必须覆盖依赖注入配置(模块)并注入模拟。这是一个如何完成的简单示例。
首先你需要一个模拟:
LoginStateManager lsmMock = mock(LoginStateManager.class);
现在覆盖 DI 配置以使用此模拟:
//Extend your TelePresenterModule, override provider method
public class TestTelePresenterModule extends TelePresenterModule{
@Override
public LoginStateManager getLoginStateManager() {
//simply return the mock here
return lsmMock;
}
}
现在开始测试:
@Test
//this is an espresso test
public void withAMock() {
//build a new Dagger2 component using the test override
TelePresenterComponent componentWithOverride = DaggerTelePresenterComponent.builder()
//mind the Test in the class name, see a class above
.telePresenterModule(new TestTelePresenterModule())
.build();
//now we initialize the dependency injector with this new config
DaggerInjectorTele.set(componentWithOverride);
mActivityRule.launchActivity(null);
//verify that injected mock was interacted with
verify(lsmMock).whatever();
}
我有一个高度依赖注入的 (dagger2) 应用程序。我想 运行 一个 espresso 测试 而不让测试浏览整个应用程序,然后登录到应用程序。
我想开始我的 teleActivity,并模拟登录管理器。但是在任何@test 函数中,我们已经在调用 onCreate 时命中了空指针。如果我在启动 activity(如下所示)之前覆盖它,则 activity 为空。
据我了解,切换下划线依赖项的能力是我们使用 Dagger2 的一个重要原因,否则它只是一个过度设计的单例。我如何覆盖、模拟或将注入切换到测试匕首模块——这样我就可以创建这个简单的浓缩咖啡测试。
请注意,如果有区别的话,我也在 MVP 设计模式中写下了所有这些内容。
TeleActivity
@Inject
TelePresenter mTelePresenter;
@Inject
public LoginStateManager mLoginStateManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ButterKnife.bind(this);
DaggerInjectorTele.get().inject(this);
mTelePresenter.setTeleDependencies(this);
Intent intent = getIntent();
String searchId = null;
if (intent != null) {
searchId = intent.getStringExtra(Constants.SEARCH_ID);
}
mTelePresenter.onCreateEvent(searchId,
Helper.makeAuthorizationHeader(
// CRASH Null pointer
mLoginStateManager.getBaseLoginResponse().getAccessToken()));
}
浓缩咖啡
@LargeTest
@RunWith(AndroidJUnit4.class)
public class TeleTest {
@Rule
public ActivityTestRule<TeleActivity> mActivityTestRule = new ActivityTestRule(
TeleActivity.class) {
@Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
TeleActivity teleActivity = (TeleActivity)getActivity();
//teleActivity NULL!
teleActivity.mLoginStateManager = mock(LoginStateManager.class);
LoginResponse loginResponse = mock(LoginResponse.class);
when(loginResponse.getAccessToken()).thenReturn("1234");
// Nope here still null
when(teleActivity.mLoginStateManager.getBaseLoginResponse()).thenReturn(loginResponse);
}
};
匕首注射器
public class DaggerInjectorTele {
private static TelePresenterComponent telePresenterComponent =
DaggerTelePresenterComponent.builder().build();
public static TelePresenterComponent get() {
return telePresenterComponent;
}
}
TelePresenterComponent
@Singleton
@Component(modules = {TelePresenterModule.class,
LoginStateManagerModule.class})
public interface TelePresenterComponent {
void inject(TeleActivity activity);
}
TelePresenterModule
@Module
public class TelePresenterModule {
@Provides
@Singleton
public TelePresenter getTelePresenter() {
return new TelePresenter();
}
}
LoginStateManagerModule
@Module
public class LoginStateManagerModule {
@Provides
@Singleton
public LoginStateManager getLoginStateManager(){
return new LoginStateManager();
}
}
似乎是架构问题而不是小问题。
首先,我不会创建静态 class 来调用 dagger2 组件,我的方法会更 android 为中心,我的意思是使用单例应用程序及其所有功能。
总之... 在没有 运行 整个工作流程的情况下 运行 测试的最佳方法是将您的项目分成两个不同的项目:
1- UI 应用程序、您的 android 活动和片段等...
使用企业架构的 2-Logic 模块说 MVP/MVC/MVVM(它应该是您 android 工作室中的另一个项目)
匕首应该用在什么地方?在您的 UI 应用程序中,用于将逻辑模块粘贴到您的 UI.
如何测试应用程序的不同部分(逻辑模块)?因为你将你的逻辑分成不同的部分,为它们编写测试会容易得多,即使你不再需要 Esperesso。使用 JUnit 和 Mockito 进行简单的单元测试可以帮助您,而无需 运行 整个工作流程。
请注意,您的 UI 应用中不应包含任何类型的逻辑。
我的观点是干净的架构:https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
我在 github 中为上述方法提供了一个简单的脚手架,您也可以阅读它,如果您喜欢的话: https://github.com/vahidhashemi/android_clean_architecture
LoginStateManager
因此,当您构建组件时,您会同时获得 TelePresenter 依赖项和 LoginStateManager 依赖项 但是两者的成员变量都没有设置值。所以我认为你需要在访问它们之前设置成员变量的值。
getBaseLoginResponse().getAccessToken())
上面的代码行给你 null 因为你没有设置值。所以在访问它之前你需要先设置值
首先,您决定使用依赖注入 (Dagger2) 是一个非常好的决定,确实会让您的测试更容易编写。
您必须覆盖依赖注入配置(模块)并注入模拟。这是一个如何完成的简单示例。
首先你需要一个模拟:
LoginStateManager lsmMock = mock(LoginStateManager.class);
现在覆盖 DI 配置以使用此模拟:
//Extend your TelePresenterModule, override provider method
public class TestTelePresenterModule extends TelePresenterModule{
@Override
public LoginStateManager getLoginStateManager() {
//simply return the mock here
return lsmMock;
}
}
现在开始测试:
@Test
//this is an espresso test
public void withAMock() {
//build a new Dagger2 component using the test override
TelePresenterComponent componentWithOverride = DaggerTelePresenterComponent.builder()
//mind the Test in the class name, see a class above
.telePresenterModule(new TestTelePresenterModule())
.build();
//now we initialize the dependency injector with this new config
DaggerInjectorTele.set(componentWithOverride);
mActivityRule.launchActivity(null);
//verify that injected mock was interacted with
verify(lsmMock).whatever();
}