user5699130
user5699130

Reputation:

Mock creation of Object inside method

Problem Description

I'm trying to mock object creation inside of method. I have LoginFragment which is creating LoginPresenterImpl inside of onCreate method, like shown below:

public class LoginFragment extends BaseFragment {
    private LoginPresenter mPresenter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mPresenter = new LoginPresenterImpl(this); <<-- Should be mocked
    }

}

I have some problems with combining RobolectricGradleTestRunner and PowerMockRunner in one test but after reading this post, I found way how to do that, so my test look like this:

BaseRobolectricTest.java

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
public abstract class BaseRobolectricTest {

}

Test.java

@PrepareForTest({LoginPresenterImpl.class})
public class Test extends BaseRobolectricTest  {

    private LoginPresenterImpl mPresenterMock;

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    @Before
    public void setup() {
        mockStatic(LoginPresenterImpl.class);

        mPresenterMock = PowerMockito.mock(LoginPresenterImpl.class);
    }

    @Test
    public void testing() throws Exception {
        when(mPresenterMock.loadUsername(any(Context.class))).thenReturn(VALID_USERNAME);
        when(mPresenterMock.loadPassword(any(Context.class))).thenReturn(VALID_PASSWORD);
        when(mPresenterMock.canAutologin(VALID_USERNAME, VALID_PASSWORD)).thenReturn(true);

        whenNew(LoginPresenterImpl.class).withAnyArguments().thenReturn(mPresenterMock);

        FragmentTestUtil.startFragment(createLoginFragment());
    }

    private LoginFragment createLoginFragment() {
        LoginFragment loginFragment = LoginFragment.newInstance();
        return loginFragment;
    }
}

Upvotes: 5

Views: 13037

Answers (4)

hunter
hunter

Reputation: 4173

I assume that you can not change the production code. so due to this bad design it is difficult achieve your requirement using a proper way.

But there is a dirty way to do this, use reflection to assign value to the private field.

public class ReflectionUtility
{
  public static void setValue(Object obj, String fieldName, Object value)
  {
    try
    {
      final Field field = obj.getClass().getDeclaredField(fieldName);
      field.setAccessible(true);
      field.set(obj, value);
    }
    catch (IllegalAccessException e)
    {
      throw new RuntimeException(e);
    }
    catch (NoSuchFieldException e)
    {
      throw new RuntimeException(e);
    }
  }
}

then in your test,



private LoginFragment createLoginFragment()
{
    LoginFragment loginFragment = LoginFragment.newInstance();
    ReflectionUtility.setValue(loginFragment, "mPresenter", mPresenterMock);
    return loginFragment;
}

Upvotes: 1

nickolay.laptev
nickolay.laptev

Reputation: 2565

Firstly if you cannot change this source code my answer will not help and you have to return to heavy mocking tools.

From coding style and design perspective I would recommend to define a factory that creates an instance of LoginPresenter. You can ask for this factory in LoginFragment constructor. And then use this factory in onCreate method. Then you can use your own implementation of this factory in unit tests that will create test implementation of LoginPresenter. That is a POJO approach that makes your code testable.

For example

public class LoginFragment extends BaseFragment {
    private LoginPresenter mPresenter;
    private final LoginPresenterFactory presenterFactory;

    public LoginFragment(LoginPresenterFactory presenterFactory) {
        this.presenterFactory = presenterFactory;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mPresenter = presenterFactory.create();
    }
}

Upvotes: 1

blue
blue

Reputation: 549

This might work, I have no way to test it though...

public class LoginFragment extends BaseFragment {
    private LoginPresenter mPresenter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mPresenter = getLoginPresenter();
    }

    protected LoginPresenter getLoginPresenter() {
        return new LoginPresenterImpl(this);
    }
}

Then in your Test.java

private LoginFragment createLoginFragment() {
    LoginFragment loginFragment = LoginFragmentTest.newInstance();
    return loginFragment;
}

private static class LoginFragmentTest extends LoginFragment {
    @Override
    protected LoginPresenter getLoginPresenter() {
        return mPresenterMock;
    }
}

Upvotes: 1

R. Zag&#243;rski
R. Zag&#243;rski

Reputation: 20258

This is just a bad code and opposite of what is "Dependency Injection" pattern.

If you used dependency injection framework like Dagger, such problem wouldn't happen as all used classed would be injected.

In test cases you would override your modules to provide mocks instead of real classes:

@Module
public class TestDataModule extends DataModule {

    public TestDataModule(Application application) {
        super(application);
    }

    @Override
    public DatabaseManager provideDatabaseManager(DatabaseUtil databaseUtil) {
        return mock(DatabaseManager.class);
    }
}

And just mock their behavior.

SOLID rules are really important in mainaining testable and reusable code.

Upvotes: 5

Related Questions