mlinhard / mockito

Automatically exported from code.google.com/p/mockito
0 stars 0 forks source link

Support "faking" abstract clases #503

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
Mockito is easy to use when the test needs to provide canned values for a 
certain method.

But it gets harder when a canned value isn't sufficient.

======Example 1: Fake with trivial Logic========

interface UserAccount {
  List<String> getEmails();
  void addEmail(String email);
  // 12 other methods ...
}

When mocking such domain entity object, it's tedious to manually program 
getEmails()/addEmail() with when().thenReturn() and to make sure the two 
methods are logically consistent, that is, getEmails() returns all emails added.

======Example 2: callback-style API======

interface AccountService {
  void getAccount(String id, AsyncCallback<UserAccount> callback);
}

Stubbing AccountService isn't easy. It'd require use of Answer, and the Answer 
API isn't statically type safe:

when(service.getAccount(eq(id), any(AsyncCallback.class)).thenAnswer(new 
Answer<Void>() {
  AsyncCallback<UserAccount> callback = (AsyncCallback<UserAccount>) getArguments()[1];
  ...
});

======Example 3: Uninteresting parameters======

interface AccountRpcService {
  FutureAccount getAccount(RpcContext context, String id);
}

None of the tests care about the context object. It's an uninteresting 
parameter imposed by the framework.

If AccountRpcService were directly mocked, all tests would have to use isA() to 
repetitively mention this uninteresting parameter, like this:

when(service.getAccount(isA(RpcContext.class), eq("id")).thenReturn(...);

And all other parameters are required to be wrapped in eq().

************Proposal************

I propose adding support for abstract classes to mockito to make it easier to 
deal with tests like above:

======For example 1======

abstract class FakeUserAccount implements UserAccount {
  private final List<String> emails = new ArrayList<>();

  @Override public void addEmail(String email) {
    emails.add(email);
  }
  @Override List<String> getEmails() {
    return ImmutableList.copyOf(emails);
  }
}

@Fake private FakeUserAccount userAccount; // Mockito instantiates abstract 
class.

======For example 2======

abstract class MockAccountService implements AccountService {
  @Override public void getAccount(String id, AsyncCallback<UserAccount> callback) {
    callback.onSuccess(getAccount(id));
  }
  abstract UserAccount getAccount(String id);
}

@Fake private MockAccountService service;

...

when(service.getAccount("id")).thenReturn(account);

======For example 3======

abstract class MockAccountRpcService implements AccountRpcService {
  @Override Future<Account> getAccount(RpcContext context, String id) {
    checkNotNull(context);  // Common sanity test. Don't have to repeat it in tests.
    return getAccount(id);
  }

  abstract Future<Account> getAccount(String id);
}

@Fake private MockAccountRpcService service;

when(service.getAccount("id")).thenReturn(...);

My work place internally implemented a default Answer to support abstract 
classes. We found that the support of abstract classes helps us to avoid 
overusing mocks when we should be using fakes. And in situations like above we 
get cleaner test code.

But because it's not integrated in the core Mockito, there are gotchas with our 
implementation (like, you can't have private/final methods in your fake).

If the idea sounds okay to give a try, I'll volunteer to submit a patch.

Thanks!

Original issue reported on code.google.com by be...@google.com on 22 Sep 2014 at 9:42

GoogleCodeExporter commented 9 years ago
Hi ben, could you submit this as an issue or PR on github, we are likely to 
drop googlecode at some point.

Thanks in advance.

Also take a look at issue 242, there have been discussion and uncertainty as to 
support partial mocks. As it can bring another way to test/design stuff the 
wrong way.

Original comment by brice.du...@gmail.com on 30 Sep 2014 at 11:30