[英]Android Instrumentation test using Mockito: mocking not working
I am trying to run an Instrumentation test (androidTest) using Mockito in Android with Java.我正在尝试使用 Android 中的 Mockito 和 Java 运行仪器测试(androidTest)。
The object under test is a simple fragment with and edittext in the layout.测试中的 object 是一个简单的片段,布局中包含和编辑文本。 The edittext is loaded from a shared preference value.
编辑文本是从共享首选项值加载的。 This is the relevant code:
这是相关代码:
public class KMCountFragment extends Fragment {
static final String SHARED_PREF_FILE = "cartrip_sharedpref";
static final String PREF_KEY_START_KM = "pref_start_km";
private SharedPreferences sharedPreferences;
private int startKMCount;
private int endKMCount;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = getContext().getSharedPreferences(SHARED_PREF_FILE, MODE_PRIVATE);
startKMCount = sharedPreferences.getInt(PREF_KEY_START_KM, 0);
}
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
...
binding.startKMCount.setText(String.valueOf(startKMCount));
}
This is the test:这是测试:
@MediumTest
@RunWith(MockitoJUnitRunner.class)
public class KMCountFragmentIT {
@Mock
Context context;
@Mock
SharedPreferences sharedPrefs;
@Before
public void setUp() {
sharedPrefs = mock(SharedPreferences.class);
context = mock(Context.class);
when(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPrefs);
}
@Test
public void testFragmentCountersInitStata() {
when(sharedPrefs.getInt(anyString(), anyInt())).thenReturn(20);
FragmentScenario<KMCountFragment> scenario = FragmentScenario.launchInContainer(KMCountFragment.class);
scenario.moveToState(Lifecycle.State.RESUMED);
onView(withId(R.id.startKMCount)).check(matches(withText("20")));
}
}
These are my dependencies in build.gradle:这些是我在 build.gradle 中的依赖项:
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment:2.3.5'
implementation 'androidx.navigation:navigation-ui:2.3.5'
implementation files('libs/activation.jar')
implementation files('libs/additionnal.jar')
implementation files('libs/mail.jar')
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.security:security-crypto:1.0.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:4.3.1'
testImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "org.mockito:mockito-android:4.3.1"
androidTestImplementation "androidx.test:core:1.4.0"
debugImplementation "androidx.fragment:fragment-testing:1.4.1"
}
The test keeps failing because the actual value in my app prefs is 999 and the test expects 20. The real context from the app is used and not the mocked one.测试一直失败,因为我的应用程序首选项中的实际值为 999,而测试预计为 20。使用的是应用程序的真实上下文,而不是模拟的上下文。 I tried to mock the whole context also but same result:
我也尝试模拟整个上下文,但结果相同:
androidx.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: 'an instance of android.widget.TextView and view.getText() with or without transformation to match: is "20"' doesn't match the selected view.
Expected: an instance of android.widget.TextView and view.getText() with or without transformation to match: is "20"
Got: view.getText() was "999" transformed text was "999"
It seems like the mocking from Mockito is not working.似乎来自 Mockito 的 mocking 不起作用。 Maybe I'm missing something stupid.. but I can't figure it out!
也许我错过了一些愚蠢的东西..但我想不通!
Your mocked Context
will not be used when Espresso spawns your activity/fragment.当 Espresso 生成您的活动/片段时,您的模拟
Context
将不会被使用。 Essentially, you cannot ( or rather should not ) mock Context in Android.本质上,您不能(或者不应该)在 Android 中模拟 Context。 I can think of three ways you can perform this test scenario.
我可以想出三种方法来执行这个测试场景。
ApplicationProvider.getApplicationContext()
to access application context in your tests and create a real SharedPreferences instance using it.ApplicationProvider.getApplicationContext()
在您的测试中访问应用程序上下文并使用它创建一个真实的 SharedPreferences 实例。 When using this approach, do not forget to clear the SharedPreferences
when tearing down the test (in the @After
method).SharedPreferences
(在@After
方法中)。SharedPreferences
in Fragment
after it has been created, by passing a callback to FragmentScenario#onFragment()
.Fragment
创建后,通过将回调传递给FragmentScenario#onFragment()
在 Fragment 中注入SharedPreferences
。 You can inject a mocked SharedPreferences
this way, but this approach tends to be more smelly than the former one.SharedPreferences
,但这种方法往往比前一种更臭。 Personally, I wouldn't recommend it unless you absolutely know what you're doing.OK, so this is my test.好的,这是我的测试。 Not so clever, but it works in my case:
不是那么聪明,但它适用于我的情况:
@MediumTest
@RunWith(MockitoJUnitRunner.class)
public class KMCountFragmentIT {
private static final int START_KM_COUNT_TEST_VALUE = 10;
private static final int END_KM_COUNT_TEST_VALUE = 90;
private SharedPreferences sharedPreferences;
private int startKMCount;
private int endKMCount;
private boolean clearPref;
@Before
public void setUp() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
sharedPreferences = appContext.getSharedPreferences(SHARED_PREF_FILE, MODE_PRIVATE);
if (sharedPreferences.contains(PREF_KEY_START_KM)) {
clearPref = false;
startKMCount = sharedPreferences.getInt(PREF_KEY_START_KM, 0);
} else {
clearPref = true;
startKMCount = START_KM_COUNT_TEST_VALUE;
SharedPreferences.Editor preferencesEditor = sharedPreferences.edit();
preferencesEditor.putInt(PREF_KEY_START_KM, startKMCount);
preferencesEditor.apply();
}
}
@After
public void tearDown() {
if (clearPref) {
sharedPreferences.edit().clear().apply();
}
}
@Test
public void testFragmentCountersInitState() {
FragmentScenario<KMCountFragment> scenario = FragmentScenario.launchInContainer(KMCountFragment.class);
scenario.moveToState(Lifecycle.State.RESUMED);
onView(withId(R.id.startKMCount)).check(matches(withText(String.valueOf(startKMCount))));
}
}
In the setup I check if the pref is present and use the value.在设置中,我检查首选项是否存在并使用该值。 In the end the fragment is the subject of the test.
最后,片段是测试的主题。 The tearDown cleans the prefs only if modified by the setup... In this way the test doesn't modify pref data if already present.
只有在被设置修改后,tearDown 才会清理首选项……这样,如果已经存在,测试就不会修改首选项数据。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.