android - Configuration change using Robolectric -
to retain asynctasks across configuration changes, use fragment-based solution setretaininstance(true), hosts each asynctask , calls listening activity, similar solution http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
ultimately, purpose test asynctask's retention functionality throughout configuration changes using robolectric, need start setting actual configuration change correctly. however, seems can't mimic exact reference behavior occurs during configuration change.
real app: when running real app, on configuration change, activity destroyed , recreated while fragment retained, seems working. can see checking references before , after configuration change (example references used below):
real app, before: activity: abc fragment: xyz
real app, after: activity: bca fragment: xyz (properly retained , reattached)
case 1: when running recreate() on activity in robolectric test, however, activity doesn't seem have instance recreated (despite docs saying method performs lifecycle calls):
mactivitycontroller = robolectric.buildactivity(asynctasktestactivity.class).attach().create().start().resume().visible(); mactivity = mactivitycontroller.get(); mactivity.recreate();
robolectric recreate(), before: activity: abc fragment: xyz
robolectric recreate(), after activity: abc fragment: xyz
this leads me believe new activity instance isn't created , reattachment functionality therefore hasn't happened in real way.
case 2: if create test based on individual lifecycle calls instead:
mactivitycontroller = robolectric.buildactivity(asynctasktestactivity.class).attach().create().start().resume().visible(); mactivitycontroller.pause().stop().destroy(); mactivitycontroller = robolectric.buildactivity(asynctasktestactivity.class).attach().create().start().resume().visible();
in version, seems activity gets replaced scratch, fragment:
robolectric separate lifecycle calls, before activity: abc fragment: xyz
robolectric separate lifecycle calls, after activity: bca fragment: yzx
it seems i'm either reusing same activity (case 1) or replacing new instances, if there no underlying application retains fragment (case 2).
question: there way can set robolectric test mimic reference result when running app in actual android environment (as per real app result), or stuck either creating separate test app or settling robotium functional tests? tried https://stackoverflow.com/a/26468296 got same result case 2.
thanks in advance!
i have played around bit , came solution using robolectric 3.0 , mockito:
@runwith(robolectricgradletestrunner.class) @config(constants = buildconfig.class, sdk = build.version_codes.kitkat, shadows = {exampleactivitytest.exampleactivityshadow.class}) public class exampleactivitytest { @mock private fragmentmanager fragmentmanagermock; @before public void setup() { initmocks(this); setupfragmentmanagermock(); } @test public void testrestoreafterconfigurationchange() { // prepare activitycontroller<exampleactivity> controller = robolectric.buildactivity(exampleactivity.class); exampleactivity activity = controller.get(); exampleactivityshadow shadow = (exampleactivityshadow) shadows.shadowof(activity); shadow.setfragmentmanager(fragmentmanagermock); activitycontroller<exampleactivity> controller2 = robolectric.buildactivity(exampleactivity.class); exampleactivity recreatedactivity = controller2.get(); exampleactivityshadow recreatedactivityshadow = (exampleactivityshadow) shadows.shadowof(recreatedactivity); recreatedactivityshadow.setfragmentmanager(fragmentmanagermock); // run & verify controller.create().start().resume().visible(); activity.findviewbyid(r.id.inc_button).performclick(); activity.findviewbyid(r.id.inc_button).performclick(); assertequals(2, activity.lostcount.count); assertequals(2, activity.retainedcount.count); bundle bundle = new bundle(); controller.saveinstancestate(bundle).pause().stop().destroy(); controller2.create(bundle).start().restoreinstancestate(bundle).resume().visible(); assertequals(0, recreatedactivity.lostcount.count); assertequals(2, recreatedactivity.retainedcount.count); } private void setupfragmentmanagermock() { final hashmap<string, fragment> fragments = new hashmap<>(); doanswer(new answer<object>() { @override public object answer(invocationonmock invocation) throws throwable { return fragments.get(invocation.getarguments()[0]); } }).when(fragmentmanagermock).findfragmentbytag(anystring()); final hashmap<string, fragment> fragmentstobeadded = new hashmap<>(); final fragmenttransaction fragmenttransactionmock = mock(fragmenttransaction.class); doanswer(new answer<object>() { @override public object answer(invocationonmock invocation) throws throwable { fragmentstobeadded.put((string) invocation.getarguments()[1], (fragment) invocation.getarguments()[0]); return fragmenttransactionmock; } }).when(fragmenttransactionmock).add(any(fragment.class), anystring()); doanswer(new answer<object>() { @override public object answer(invocationonmock invocation) throws throwable { fragments.putall(fragmentstobeadded); return null; } }).when(fragmenttransactionmock).commit(); when(fragmentmanagermock.begintransaction()).thenreturn(fragmenttransactionmock); } @implements(activity.class) public static class exampleactivityshadow extends shadowactivity { private fragmentmanager fragmentmanager; @implementation public fragmentmanager getfragmentmanager() { return fragmentmanager; } public void setfragmentmanager(fragmentmanager fragmentmanager) { this.fragmentmanager = fragmentmanager; } } }
note have mocked methods of fragmentmanager (begintransaction()
, findfragmentbytag()
) , fragmenttransaction (add()
, commit()
) use in code, might need expand these depending on code.
i haven't done work robolectric yet, there may more elegant solution this, works me now.
you can see full source code , project setup here: https://github.com/rgeldmacher/leash (might worth if still need retain objects ;) )
Comments
Post a Comment