gpt4 book ai didi

android - InjectView 在单元测试中不起作用

转载 作者:行者123 更新时间:2023-12-03 06:17:58 26 4
gpt4 key购买 nike

tl;博士:在单元测试中,@Inject成功注入(inject)所需字段,但 @InjectView将它们保留为空,从而导致错误。 @InjectView在正常的应用程序运行中工作。

我有一个使用 RoboGuice 的 Android 应用程序,我正在尝试对其进行单元测试。我尝试测试的一项 Activity 如下所示:

@ContentView(R.layout.activity_login)
public class LoginActivity extends RoboActivity {

/**
* Our injected API class.
*/
@Inject
private Api apiConn;

/**
* Main application state class.
*/
@Inject
private Application appState;

/**
* Keep track of the login task to ensure we can cancel it if requested.
*/
private UserLoginTask mAuthTask = null;

/**
* Logo header
*/
@InjectView(R.id.logoHeader)
private TextView mLogoHeader;

/**
* Signin button.
*/
@InjectView(R.id.email_sign_in_button)
private Button mEmailSignInButton;

/**
* Email text view.
*/
@InjectView(R.id.email)
private EditText mEmailView;

/**
* Password text view.
*/
@InjectView(R.id.password)
private EditText mPasswordView;

/**
* Login progress view.
*/
@InjectView(R.id.login_progress)
private View mProgressView;

/**
* Login form view.
*/
@InjectView(R.id.login_form)
private View mLoginFormView;

/**
* Fired when Activity has been requested.
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Check if the server is actually online
PingTask ping = new PingTask();
ping.execute();

// Check if the user is already logged in.
if (appState.hasLoginToken() && appState.getLoginToken().isValid()) {
// Hide everything!
mLogoHeader.setVisibility(View.INVISIBLE);
mEmailView.setVisibility(View.INVISIBLE);
mPasswordView.setVisibility(View.INVISIBLE);
mEmailSignInButton.setVisibility(View.INVISIBLE);

// The user already has a token, let's validate it and pull in extra information.
AuthenticateTask authenticate = new AuthenticateTask();
authenticate.execute();
return;
}
appState.clearSettings();

// Set up the login form.
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
}
});

mEmailSignInButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
attemptLogin();
}
});
}

/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
void attemptLogin() {
if (mAuthTask != null) {
return;
}

// Reset errors.
mEmailView.setError(null);
mPasswordView.setError(null);

// Store values at the time of the login attempt.
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();

boolean cancel = false;
View focusView = null;

// Check for a valid password, if the user entered one.
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
mPasswordView.setError(getString(R.string.error_invalid_password));
focusView = mPasswordView;
cancel = true;
}

// Check for a valid email address.
if (TextUtils.isEmpty(email)) {
mEmailView.setError(getString(R.string.error_field_required));
focusView = mEmailView;
cancel = true;
} else if (!isEmailValid(email)) {
mEmailView.setError(getString(R.string.error_invalid_email));
focusView = mEmailView;
cancel = true;
}

if (cancel) {
// There was an error; don't attempt login and focus the first
// form field with an error.
focusView.requestFocus();
} else {
// Show a progress spinner, and kick off a background task to
// perform the user login attempt.
showProgress(true);
mAuthTask = new UserLoginTask(email, password);
mAuthTask.execute((Void) null);
}
}

/**
* Checks if a given email address is valid.
*
* @param email Email address to check.
*
* @return Whether the email is valid or not.
*/
private boolean isEmailValid(String email) {
String ePattern = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$";

Pattern p = Pattern.compile(ePattern);
Matcher m = p.matcher(email);

return m.matches();
}

/**
* Checks if a given password is valid.
*
* @param password Password to check.
*
* @return Whether the password is valid or not.
*/
private boolean isPasswordValid(CharSequence password) {
//TODO: Is this needed?
return password.length() > 4;
}

/**
* Shows the progress UI and hides the login form.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
void showProgress(final boolean show) {
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
// for very easy animations. If available, use these APIs to fade-in
// the progress spinner.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);

mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
mLoginFormView.animate().setDuration(shortAnimTime).alpha(
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
});

mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mProgressView.animate().setDuration(shortAnimTime).alpha(
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
} else {
// The ViewPropertyAnimator APIs are not available, so simply show
// and hide the relevant UI components.
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}

/**
* Stop hardware back button functionality.
*/
@Override
public void onBackPressed() {
}

/**
* User Login Task.
*/
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {

/**
* Email to login with.
*/
private final String mEmail;

/**
* Password to login with.
*/
private final String mPassword;

/**
* Constructor.
*
* @param email Email to login with.
* @param password Password to login with.
*/
public UserLoginTask(String email, String password) {
mEmail = email;
mPassword = password;
}

/**
* Main task.
*
* @param params Parameters.
*
* @return Whether login was successful or not.
*/
@Override
protected Boolean doInBackground(Void... params) {
JSONObject responseJson;
boolean success;
String token;
JSONArray drivers;
JSONArray moods;
JSONArray vehicles;
try {
// Contact API.
String response = apiConn.login(mEmail, mPassword);
responseJson = new JSONObject(response);
success = responseJson.getBoolean("success");
JSONObject data = responseJson.getJSONObject("data");
token = data.getString("token");
drivers = data.getJSONArray("drivers");
moods = data.getJSONArray("moods");
vehicles = data.getJSONArray("vehicles");
} catch (ApiException e) {
return false;
} catch (JSONException e) {
return false;
}

if (!success) {
return false;
}

// Persist data.
appState.setLoginToken(token);
appState.setDrivers(drivers);
appState.setMoods(moods);
appState.setVehicles(vehicles);
return true;
}

/**
* Fired after the task has executed.
*
* @param success Whether the task was successful.
*/
@Override
protected void onPostExecute(final Boolean success) {
mAuthTask = null;
showProgress(false);

if (success) {
// Start chooser activity
Intent chooserIntent = new Intent(getApplicationContext(), ChooserActivity.class);
startActivity(chooserIntent);

// Close task.
finish();
} else {
mPasswordView.setError(getString(R.string.error_incorrect_password));
mPasswordView.requestFocus();
}
}

/**
* Fired when the task was cancelled.
*/
@Override
protected void onCancelled() {
mAuthTask = null;
showProgress(false);
}

}

/**
* Ping task to test if API is available.
*/
public class PingTask extends AsyncTask<Void, Void, Boolean> {

/**
* Alert dialog.
*/
private AlertDialog.Builder alertDialog;

/**
* Fired before executing task.
*/
@Override
protected void onPreExecute() {
super.onPreExecute();

alertDialog = new AlertDialog.Builder(LoginActivity.this);
}

/**
* Main task.
*
* @param params Parameters.
*
* @return Whether the API is online or not.
*/
@Override
protected Boolean doInBackground(Void... params) {
boolean webOnline = apiConn.isWebOnline();
Log.d("API", String.valueOf(webOnline));

return webOnline;
}

/**
* Fired after task was executed.
*
* @param success Whether the task was successful.
*/
@Override
protected void onPostExecute(final Boolean success) {
super.onPostExecute(success);

Log.d("API", String.valueOf(success));
if (!success) {
alertDialog.setTitle(getString(R.string.cannot_connect));
alertDialog.setMessage(getString(R.string.cannot_connect_long));

AlertDialog dialog = alertDialog.create();
dialog.show();
}
}
}

/**
* Authenticate with existing token task.
*/
class AuthenticateTask extends AsyncTask<Void, Void, Boolean> {

/**
* Array of drivers from Api.
*/
private JSONArray drivers;

/**
* Array of moods from Api.
*/
private JSONArray moods;

/**
* Array of vehicle from Api.
*/
private JSONArray vehicles;

/**
* Main task.
*
* @param params Parameters.
*
* @return Whether the task was successful or not.
*/
@Override
protected Boolean doInBackground(Void... params) {
JSONObject responseJson;
boolean success;
drivers = null;
moods = null;
vehicles = null;
try {
String response = apiConn.authenticate(appState.getLoginToken());
responseJson = new JSONObject(response);
success = responseJson.getBoolean("success");
JSONObject data = responseJson.getJSONObject("data");
drivers = data.getJSONArray("drivers");
moods = data.getJSONArray("moods");
vehicles = data.getJSONArray("vehicles");
} catch (ApiException | JSONException e) {
success = false;
}

return success;
}

/**
* Fired after the task has been executed.
*
* @param success Whether the task was successful.
*/
@Override
protected void onPostExecute(Boolean success) {
super.onPostExecute(success);

if (!success) {
Log.d("JWT", "Token Invalid");
// Token is invalid! Clear and show login form.
appState.clearSettings();

// Show the form again.
mLogoHeader.setVisibility(View.VISIBLE);
mEmailView.setVisibility(View.VISIBLE);
mPasswordView.setVisibility(View.VISIBLE);
mEmailSignInButton.setVisibility(View.VISIBLE);

return;
}

appState.setDrivers(drivers);
appState.setMoods(moods);
appState.setVehicles(vehicles);

Toast toast = Toast.makeText(getApplicationContext(), getString(R.string.welcome_back), Toast.LENGTH_SHORT);
toast.show();

// Start chooser activity
Intent chooserIntent = new Intent(LoginActivity.this, ChooserActivity.class);
startActivity(chooserIntent);
}
}
}

问题在于 onCreate方法崩溃说 mPasswordView在这一行为空:
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {

原来mPasswordView其实是 null .它应该由 RoboGuice 的 @InjectView 设置。当我在我的设备上使用应用程序时有效但不适用于单元测试的注释。 @Inject然而正在工作......

如何让我的单元测试通过 @InjectView 解析 View ?

补课

这是我的 LoginActivityTest 文件:
@Config(manifest = "app/src/main/AndroidManifest.xml", emulateSdk = 18, reportSdk = 18)
@RunWith(RobolectricGradleTestRunner.class)
public class LoginActivityTest {

private final Api apiMock = mock(Api.class);

private final Application appMock = mock(Application.class);

private final ActivityController<LoginActivity> controller = buildActivity(LoginActivity.class);

@Before
public void setup() {
RoboGuice.overrideApplicationInjector(Robolectric.application, new LoginTestModule());
}

@After
public void teardown() {
RoboGuice.Util.reset();
}

@Test
public void createTriggersPing() {
ActivityController controller = Robolectric.buildActivity(LoginActivity.class);
controller.create();
controller.start();
verify(apiMock).isWebOnline();
}

private class LoginTestModule extends AbstractModule {

@Override
protected void configure() {
bind(Api.class).toInstance(apiMock);
bind(Application.class).toInstance(appMock);
}
}
}

和我的 RobolectricGradleTestRunner 类:
public class RobolectricGradleTestRunner extends RobolectricTestRunner {

public RobolectricGradleTestRunner(Class<?> testClass) throws org.junit.runners.model.InitializationError {
super(testClass);
}

@Override
protected AndroidManifest getAppManifest(Config config) {
String manifestProperty = System.getProperty("android.manifest");
if (config.manifest().equals(Config.DEFAULT) && manifestProperty != null) {
String resProperty = System.getProperty("android.resources");
String assetsProperty = System.getProperty("android.assets");

return new AndroidManifest(Fs.fileFromPath(manifestProperty), Fs.fileFromPath(resProperty),
Fs.fileFromPath(assetsProperty));
}
return super.getAppManifest(config);
}
}

最佳答案

事实证明,这与 Roboguice 本身无关。 OP 的设置存在一些特定的错误配置。通过聊天解决了。问题是使用 Roboguice 的 @ContentView (它显然不能很好地用于测试,我以前也遇到过同样的情况)和一个配置错误的 XML 文件。

关于android - InjectView 在单元测试中不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28840439/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com