Skip to content
This repository was archived by the owner on Feb 26, 2023. It is now read-only.
This repository was archived by the owner on Feb 26, 2023. It is now read-only.

Improve testability by letting provide overrides for injected implementations #989

Open

Description

@ralphhm

It would be nice to provide override implementations (e.g. mock instances) when using the builder to construct an enhanced component instance. Imagine a fragment where you want to inject a Tracking instance:

@EFragment(R.layout.tracked_fragment)
public class TrackedFragment extends Fragment {

    @Bean
    protected Tracking tracking;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        tracking.trackView();
    }
}

In production code i would use this fragment like this:

fragment = TrackingFragment_.builder().build();

There will be a problem if you want to use it in a robolectric test like this:

public class TrackingFragmentTest {

    private TrackingFragment fragment;

    @Before
    public void setUp() {
        FragmentActivity activity = Robolectric.setupActivity(FragmentActivity.class);
        fragment = TrackingFragment_.builder().build();
        fragment.tracking = mock(Tracking.class); //option1
        activity.getSupportFragmentManager().beginTransaction().add(fragment, null).commit();
        fragment.tracking = mock(Tracking.class); //option2
    }
}

Using option 1 doesn't do anything because the reference is overriden with a real Tracking instance by AA when the fragment transaction takes place. Option 2 leads to a call to trackView of the real Tracking instance because the call to onCreate() happens when the fragment transaction takes place - the manual injection happens to late.
A nice and elegant solution which offers enhanced injection capabilities would be to have something like a use, provide or override method in the builder:

TrackingFragment_.builder().use(mock(Tracking.class)).build();

The builder could save the provided instances in a protected collection field of the generated enhanced component like this:

    public static class FragmentBuilder_ {

        private Bundle args_;
        private List instances = new ArrayList();

        private FragmentBuilder_() {
            args_ = new Bundle();
        }

        public LoginFragment build() {
            LoginFragment_ fragment_ = new LoginFragment_();
            if(instances != null)
                fragment_.instances = instances.toArray();
            fragment_.setArguments(args_);
            return fragment_;
        }

        public void use(Object providedInstance) {
            instances.add(providedInstance);
        }

    }

The init_ method of the generated enhanced component could look like this:

    private void init_(Bundle savedInstanceState) {
        OnViewChangedNotifier.registerOnViewChangedListener(this);
        tracking = findInstance(Tracking_.getInstance_(getActivity()));
    }

    private <T> T findInstance(T fallback) {
        if (instances != null) {
            for(Object instance : instances) {
                if(fallback.getClass().isInstance(instance))
                    return (T) instance;
            }
        }
        return fallback;
    }

Activity

  1. sterlp commented on May 10, 2016

    @sterlp

    Observing the same issue here, just forked the code and will try to provide a patch. Currently it is easy to test simple activities. As soon it gets more complex where is not really a good way to mock it.

    I would suggest a full refactoring to a service locater pattern (e.g. like in Spring or Java EE) anyway the two major problems for mocks now are:

    • The extended Bean is final -- just removing the final would allow you to mock singleton beans by setting the instance_ via reflection
    • As already mentioned above setting beans etc. over package private accessors or setter wont work in more complex scenarios due to the lifecycle of some views

    On the last point some more detail: As soon as you call getActivity in your test the whole thing starts and the mocks are sometimes not in the right state, you will see Nullpointers and other stuff.

  2. sterlp commented on May 10, 2016

    @sterlp

    Something like this should do the trick:

    sterlp@4924378

    My problem is that the project didn't compile at my machine - no idea why.

    In addition you have to set generateFinalClasses to true:

    apt {
        arguments {
            generateFinalClasses 'false'
        }
    }
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions