Việc chạy unit test trên thiết bị thật hoặc emulator trong Android thường rất chậm do phải đóng gói APK và khởi động toàn bộ môi trường Android. Robolectric giải quyết vấn đề này bằng cách cung cấp một môi trường giả lập trên JVM, thay thế các lời gọi hệ thống Android bằng các "shadow" class – những lớp mô phỏng hành vi của framework gốc nhưng có thể chạy hoàn toàn trên máy ảo Java.
Cấu hình dự án
Trong file build.gradle (module-level), thêm cấu hình sau:
android {
testOptions {
unitTests {
includeAndroidResources = true
all {
jvmArgs '-noverify'
}
}
}
}
dependencies {
testImplementation 'org.robolectric:robolectric:4.3'
}
Với Android Studio 3.3 trở lên, không cần thêm dòng android.enableUnitTestBinaryResources=true vào gradle.properties.
Cấu hình lớp kiểm thử
Mỗi lớp test cần được chú thích với @RunWith(RobolectricTestRunner.class) và có thể tùy chỉnh môi trường qua @Config:
@RunWith(RobolectricTestRunner.class)
@Config(sdk = Build.VERSION_CODES.M)
public class MainActivityTest {
// Các phương thức kiểm thử
}
Các tình huống kiểm thử phổ biến
1. Cấu hình môi trường kiểm thử
Robolectric hỗ trợ nhiều tùy chọn qua @Config:
- SDK version:
@Config(sdk = Build.VERSION_CODES.KITKAT) - Application class:
@Config(application = CustomApp.class) - Tài nguyên đặc tả:
@Config(qualifiers = "zh-rCN")để kiểm thử chuỗi ngôn ngữ Trung Quốc
Có thể thay thế chú thích bằng file robolectric.properties đặt trong src/test/resources:
sdk=21
qualifiers=zh-rCN
2. Kiểm thử chuyển Activity
Giả sử có nút bấm trong MainActivity khởi chạy LoginActivity:
@Test
public void shouldStartLoginActivityWhenLoginButtonClicked() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
activity.findViewById(R.id.btn_login).performClick();
Intent startedIntent = shadowOf(RuntimeEnvironment.application)
.getNextStartedActivity();
assertThat(startedIntent.getComponent())
.isEqualTo(new ComponentName(activity, LoginActivity.class));
}
3. Xử lý Toast và Dialog
Kiểm tra nội dung Toast:
@Test
public void shouldShowToastWithCorrectMessage() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
activity.findViewById(R.id.btn_login).performClick();
assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo("Đăng nhập thành công");
}
Kiểm tra AlertDialog:
@Test
public void shouldDisplayAlertDialog() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
activity.findViewById(R.id.btn_login).performClick();
AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
assertThat(dialog).isNotNull();
assertThat(shadowOf(dialog).getMessage()).isEqualTo("Xác nhận đăng nhập?");
}
4. Kiểm thử Fragment
Với Fragment sử dụng thư viện hỗ trợ:
@Test
public void shouldDisplayPassedArgumentInFragment() {
Bundle args = new Bundle();
args.putString("name", "Nguyễn Văn A");
TestFragment fragment = TestFragment.newInstance(args);
SupportFragmentTestUtil.startFragment(fragment);
TextView nameView = fragment.getView().findViewById(R.id.tv_name);
assertThat(nameView.getText().toString()).isEqualTo("Nguyễn Văn A");
}
Yêu cầu thêm phụ thuộc nếu dùng AndroidX hoặc Support Library:
testImplementation 'org.robolectric:shadows-supportv4:4.3'
5. Quản lý vòng đời Activity
Sử dụng ActivityController để điều khiển từng giai đoạn vòng đời:
@Test
public void shouldUpdateStateDuringLifecycle() {
ActivityController<MainActivity> controller =
Robolectric.buildActivity(MainActivity.class);
controller.create();
assertThat(activity.getState()).isEqualTo("created");
controller.start();
assertThat(activity.getState()).isEqualTo("started");
controller.resume();
assertThat(activity.getState()).isEqualTo("resumed");
}
6. Kiểm thử BroadcastReceiver
Đối với LocalBroadcastManager:
@Test
public void shouldSaveDataWhenReceivingValidBroadcast() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
LocalBroadcastManager manager =
ShadowLocalBroadcastManager.getInstance(activity);
Intent intent = new Intent(MainActivity.ACTION_SAVE_DATA);
intent.putExtra("user_id", "12345");
manager.sendBroadcast(intent);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
assertThat(prefs.getString("user_id", "")).isEqualTo("12345");
}
7. Kiểm thử Service
Xác minh việc khởi chạy Service:
@Test
public void shouldStartBackgroundServiceOnButtonClick() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
activity.findViewById(R.id.btn_start_service).performClick();
Intent expected = new Intent(activity, BackgroundService.class);
Intent actual = ShadowApplication.getInstance().getNextStartedService();
assertThat(actual.getComponent()).isEqualTo(expected.getComponent());
}
8. Thao tác với cơ sở dữ liệu và tệp tin
Robolectric tự động tạo môi trường lưu trữ tạm thời cho các thao tác I/O:
@Test
public void shouldReadWriteFileCorrectly() {
FileManager manager = new FileManager();
String testContent = "Nội dung kiểm thử";
String filePath = Environment.getExternalStorageDirectory() + "/test.txt";
manager.write(filePath, testContent);
assertThat(manager.read(filePath)).isEqualTo(testContent);
}
9. Xử lý yêu cầu mạng bất đồng bộ
Để kiểm thử logic xử lý phản hồi API, có thể sử dụng interceptor hoặc mock callback:
@Test
public void shouldShowSuccessMessageOnValidApiResponse() {
// Thiết lập interceptor trả về JSON mẫu
TestInterceptor.setMode(TestInterceptor.MODE_SUCCESS);
MusicChannelActivity activity = Robolectric.setupActivity(MusicChannelActivity.class);
new MusicChannelPresenter(activity).requestChannels();
// Chạy tác vụ bất đồng bộ ngay lập tức
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
TextView resultView = activity.findViewById(R.id.result_text);
assertThat(resultView.getText().toString()).isEqualTo("Tải dữ liệu thành công");
}
10. Tùy chỉnh Shadow Class
Tạo lớp shadow cho lớp tùy chỉnh:
@Implements(Person.class)
public class ShadowPerson {
@RealObject Person realPerson;
@Implementation
public String getName() {
return "Người dùng mặc định"; // Ghi đè giá trị gốc
}
public String getOriginalName() {
return realPerson.name; // Truy cập trực tiếp field
}
}
Áp dụng trong test:
@Config(shadows = ShadowPerson.class)
@Test
public void shouldReturnMockedName() {
Person person = new Person("Nguyễn Văn B");
assertThat(person.getName()).isEqualTo("Người dùng mặc định");
}
11. Tối ưu hóa cấu hình chung
Tạo lớp cơ sở cho tất cả bài kiểm thử:
@RunWith(RobolectricTestRunner.class)
@Config(sdk = Build.VERSION_CODES.M)
public abstract class BaseTest {
protected Application getApp() {
return RuntimeEnvironment.application;
}
protected <T extends Activity> T startActivity(Class<T> cls) {
return Robolectric.setupActivity(cls);
}
}
Lưu ý quan trọng
- Không nên mock các lớp hệ thống Android như Context hay SharedPreferences
- Tránh kiểm thử layout inflation – tập trung vào logic tương tác
- Luôn dọn dẹp luồng nền sau mỗi bài test để tránh rò rỉ tài nguyên
- Sử dụng kết hợp Mockito/PowerMockito để xử lý các phương thức tĩnh hoặc final