9

I've been trying to figure out how to unit test my BroadcastReceiver and I have looked at StackOverflow and other websites but I can't find the solution to my problem.

In my mainActivity I have the following two functions:

private void registerNetRcvr(){

    if (!isRcvrRegistered) {        
        isRcvrRegistered = true;
        registerReceiver(receiver, new IntentFilter("android.net.wifi.STATE_CHANGE"));
        registerReceiver(receiver, new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"));
    }
}

private BroadcastReceiver receiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {

        NetworkHandler.NetworkInfo netInfo = NetworkHandler.handleBcastReceiver(context);

        if (netInfo!=null){
            handleInfoChange(netInfo);
        } else {
            handleInfoChange(null);
        }
    }
};

The registerNetRcvr is called from within the onResume function (and equally I have an unregister called from onPause).

As can be seen from the above, I have a function (handleBcastReceiver) that is called to handle the onReceive event and thus have another class that then has a variety of private functions which are called from this function.

Just to slightly complicate matters... upon the onReceive function being called, as detailed above the 'handleBcastReceiver' function would then need to 'retreive' the correct data... and, as such would make a call to actually retrieve the appropriate system data as follows:

private static NetworkInfo getWiFiNetworkInfo(Context context) {

    ConnectivityManager connManager = (ConnectivityManager)
            context.getSystemService(Context.CONNECTIVITY_SERVICE);
    return connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
}

I believe that I should emulate the behaviour of the onReceive callback but also I guess emulate what would be returned from the system during the call to getWiFiNetworkInfo.

My thinking is that I should be using RoboElectric to do this as it can shadow the BroadcastReceiver/ConnectivityManager. I've also looked at Mockito however I believe that RoboElectric is what I require for this but I accept that I could well be wrong. As far as I'm aware, RoboElectric allows me to emulate 'the Android system' and Mockito would allow me to mock my own classes.

At present I just can't see how to test the BroadcastReceiver. Additionally I'm not clear upon whether I should be running the emulator to do this or simply 'run' my unit test as the 'shadow' should contain everything I need. Finally, if I was to shadow the BroadcastReceiver, how do I get WIFI to be 'enabled' or 'disabled' through this shadow process? In fact, should I be emulating a BroadcastReceiver or just the ConnectivityManager... I'm genuinely confused!

What I have done so far is to create a BroadcastReceiverTest class within the test section of my app (not the 'androidTest' section). I can do normal simple unit tests on functions, I'm just stuck with how to emulate this 'system' behaviour.

As always, any help is greatly appreciated.

greysqrl
  • 937
  • 3
  • 13
  • 31
  • There is problem - in order to test android app, you need to right it in specific, testable way. That's why MVP and technic like that is very popular right now. – Divers Jan 16 '17 at 10:52
  • A couple of issues with the BroadcastReceiver. Targeting CONNECTIVITY_CHANGE is deprecated for apps targeting N or greater. Also the receiver should check the action it gets is what it it is subscribed to - to avoid malicious requests. – Oren Bochman Nov 14 '18 at 06:48

1 Answers1

8

So, I think I've solved it... :) If anyone with more experience wishes to give me feedback on this, it'd be much appreciated.

I feel that I don't need to care about the 'broadcast receiver' more so I need to be bothered with shadowing the ConnectivityManager as this is what the 'handleBcastReceiver' function will ultimately read from. So, really that is what is important as it's the 'feed' into my functions which drives how they behave. When the onReceive function is triggered, all it will do is 'handleBcastReceiver' function and then update the UI with the result, and hence it's not what I really need to test here.

I created the following and it's working for me. If I modify my test data, tests will fail as appropriate so I believe that this is satisfactory. I hope others may find it useful and as I mentioned, I'm very happy to receive feedback on whether what I've done is or isn't correct.

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23, manifest = "/src/main/AndroidManifest.xml")
public class NetworkStateHandlerUnitTest {

    private static String NETWORK_TYPE_HSDPA = "HSDPA";
    private ConnectivityManager connectivityManager;
    private ShadowConnectivityManager shadowConnectivityManager;
    private Context context;

    @Before
    public void setUp() {

    }

    @Test
    public void handleBroadcastReceiverWIFIConnected() {

        context = RuntimeEnvironment.application.getApplicationContext();
        connectivityManager = getConnectivityManager();
        shadowConnectivityManager = Shadows.shadowOf(connectivityManager);

        //Create shadow Network Info - Connected, WIFI, No Subtype, available, connected
        NetworkInfo networkInfoShadow = ShadowNetworkInfo.newInstance(NetworkInfo.DetailedState.CONNECTED, ConnectivityManager.TYPE_WIFI, 0, true, true);
        shadowConnectivityManager.setNetworkInfo(ConnectivityManager.TYPE_WIFI, networkInfoShadow);

        NetworkHandler.NetworkInfo networkInfoResponse;

        //Call the function under test
        networkInfoResponse = NetworkHandler.handleBcastReceiver(context);

        //Validate the response
        assertEquals(networkInfoResponse.getNetworkType(), ConnectivityManager.TYPE_WIFI);
    }

    private ConnectivityManager getConnectivityManager() {
        return (ConnectivityManager) RuntimeEnvironment.application.getSystemService(context.CONNECTIVITY_SERVICE);
    }
}

I do, of course have more than just this one test but felt this would be enough to help anyone else who finds themselves similarly stuck with this :)

Pleased to say that eventually I managed to test the BroadcastReceiver listener as well... in case you want to know how to do this...

@Test
public void handleBrcastRcvrWifiConn() {

    MainActivity activity = Robolectric.setupActivity(MainActivity.class);
    TextView tvConnStatus = (TextView) activity.findViewById(R.id.tvConnectionState);


    context = RuntimeEnvironment.application.getApplicationContext();
    connectivityManager = getConnectivityManager();
    shadowConnManager = Shadows.shadowOf(connectivityManager);

    //Create shadow Network Info - Connected, WIFI, No Subtype, available, connected
    NetworkInfo netInfoShadow = ShadowNetworkInfo.newInstance(NetworkInfo.DetailedState.CONNECTED,
            ConnectivityManager.TYPE_WIFI, 0, true, true);
    shadowConnManager.setNetworkInfo(ConnectivityManager.TYPE_WIFI, networkInfoShadow);


    //Trigger BroadcastReceiver
    RuntimeEnvironment.application.sendBroadcast(new Intent("android.net.wifi.STATE_CHANGE"));

    //Validate result by checking value of textView
    assertEquals("Connected", tvConnectionState.getText());

}
greysqrl
  • 937
  • 3
  • 13
  • 31
  • I should note that a lot of what I learnt came from this post... http://stackoverflow.com/questions/30625378/shadownetworkinfo-is-always-type-mobile-when-testing-wifi-connectivity-with-robo – greysqrl Jan 16 '17 at 14:18