robolectric / robolectric

Android Unit Testing Framework
http://robolectric.org
Other
5.87k stars 1.36k forks source link

Registered LocalBroadcastReceiver not receiving intents after upgrading to robolectric 4.5.1 (4.4.1 and above) from 4.3.1 #7096

Closed fhsiao closed 2 years ago

fhsiao commented 2 years ago

Description

Upgrading robolectric from 4.3.1 to 4.5.1 (also tested 4.4.1 and above) , LocalBroadcastReceiver registered receiver no longer receives intents when running test cases. On a physical phone, the intents were received.

Code: `public class MyActivity extends Activity {

Button mClickMeButton;
TextView mHelloWorldTextView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    System.out.println("==== onCreate is called");
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_my);
    mHelloWorldTextView = (TextView) findViewById(R.id.helloWorldTextView);
    mClickMeButton = (Button) findViewById(R.id.clickMeBtn);
    mClickMeButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            System.out.println("==== onClick is called");
            mHelloWorldTextView.setText("HEY WORLD");
            sendIntent();
        }
    });
    LocalBroadcastManager.getInstance(this).registerReceiver(aLBReceiver,
            new IntentFilter("anEvent"));
}
@Override
protected void onDestroy() {
    System.out.println("==== onDestroy is called");
    // Unregister since the activity is about to be closed.
    LocalBroadcastManager.getInstance(this).unregisterReceiver(aLBReceiver);
    super.onDestroy();
}

void sendIntent (){
    System.out.println("==== sendIntent is called");
    Intent intent = new Intent("anEvent");
    intent.putExtra("key", "This is an event");
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}

private BroadcastReceiver aLBReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        System.out.println("==== Intent is received");
        mHelloWorldTextView.setText("Received an Intent");
    }
};

}`

My testing code:

`@RunWith(RobolectricTestRunner.class) public class MyActivityTest {

private MyActivity mActivity;

@Before
public void setup() {
    mActivity = Robolectric.buildActivity(MyActivity.class).create().get();
}

@Test
public void myActivityAppearsAsExpectedInitially() {
    assertThat(mActivity.mClickMeButton).hasText("Click me!");
    assertThat(mActivity.mHelloWorldTextView).hasText("Hello world!");
}

@Test
public void clickingClickMeButtonChangesHelloWorldText() {
    assertThat(mActivity.mHelloWorldTextView).hasText("Hello world!");
    mActivity.mClickMeButton.performClick();
    assertThat(mActivity.mHelloWorldTextView).hasText("HEY WORLD");
}

}`

Settings: `apply plugin: 'com.android.application' android { compileSdkVersion 30 buildToolsVersion "30.0.2"

defaultConfig {
    applicationId "com.example.test.myapplication"
    minSdkVersion 29
    targetSdkVersion 30
    versionCode 1
    versionName "1.0"
}

buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

testOptions {
    unitTests {
        includeAndroidResources = true
    }
}

}

dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'com.squareup.assertj:assertj-android:1.1.0' testImplementation 'org.robolectric:robolectric:4.5.1' testImplementation 'org.mockito:mockito-inline:3.9.0' implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' }`

Testing failed and onReceive(Context context, Intent intent) is not called.

    private BroadcastReceiver aLBReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            System.out.println("==== Intent is received");
            mHelloWorldTextView.setText("Received an Intent");
        }
    };

Steps to Reproduce

When changing back to the version of 4.3.1, onReceive(Context context, Intent intent) will be called and intents will be received.

With current testImplementation 'org.robolectric:robolectric:4.5.1':

Change to testImplementation 'org.robolectric:robolectric:4.3.1':

Robolectric & Android Version

Robolectric: 4.5.1 Android Q

Link to a public git repo demonstrating the problem:

https://github.com/fhsiao/robolectricbug

utzcoz commented 2 years ago

Hi @fhsiao , could you help to test it on 4.7.3? The 4.5.1 is old now. If 4.7.3 also has this problem, you can attach a reproduce-app by following https://github.com/robolectric/robolectric/wiki/How-to-Report-Issues, and we can analyze this problem based on your provided sample.

fhsiao commented 2 years ago

Hi @utzcoz , I have tried 4.7.3 and the results are the same (observed in 4.4.1 and above). The issue should be reproducible by using the sample app linked above. One thing noticed is that "Robolectric currently supports APIs: 16, 17, 18, 19 and 21" but my app has to use minSdkVersion 29. Would that be an issue?

The issue can been seen using Shadows, however it seems the root cause is that the receiver didn't receive intents during testings. List<Intent> intents = Shadows.shadowOf(context).getBroadcastIntents();

utzcoz commented 2 years ago

@fhsiao Looks like LocalBroadcastReceiver manages broadcast internally(https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-viewpager2-release/localbroadcastmanager/src/main/java/androidx/localbroadcastmanager/content/LocalBroadcastManager.java#218, https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-viewpager2-release/localbroadcastmanager/src/main/java/androidx/localbroadcastmanager/content/LocalBroadcastManager.java#278), and it doesn't use system's mechanism to manage broadcast intents. I think if you use LocalBroadcastManager#sendBroadcastSync you can receive onReceive calling. If sendBroadcastSync works for you, could you try to add @LooperMode(LooperMode.Mode.LEGACY) to your test class to use legacy looper mode? Looks like Robolectric 4.4 started to use paused mode for looper default(https://github.com/robolectric/robolectric/releases/tag/robolectric-4.4), and it maybe affect LocalBroadcastManager's mechanism to executing receivers' onReceive method to send broadcast intent based on Handler(https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-viewpager2-release/localbroadcastmanager/src/main/java/androidx/localbroadcastmanager/content/LocalBroadcastManager.java#280).

fhsiao commented 2 years ago

@utzcoz Thank you for the suggestions.

As mentioned, intents were received by using sendBroadcastSync. However @LooperMode(LooperMode.Mode.LEGACY) has been deprecated. https://github.com/robolectric/robolectric/pull/5490

Since currently we are using sendBroadcast and there could be implications of using the sendBroadcastSync instead such as the blocking natural, is there any other alternative solutions that could be implemented in the test cases to solve the issue?

Thank you!

utzcoz commented 2 years ago

@fhsiao Okay, your testing result confirmed by guess, and it is caused by new looper mode. You can follow http://robolectric.org/blog/2019/06/04/paused-looper/ to migrate your test code to work with new looper mode.

fhsiao commented 2 years ago

@utzcoz Thank you for looking into this issue. Its confirmed that the issue has been resolved by http://robolectric.org/blog/2019/06/04/paused-looper/ by design.