Eedanna / mockito

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

Provide a default Answer for mocking abstract classes #242

Open GoogleCodeExporter opened 8 years ago

GoogleCodeExporter commented 8 years ago
For your consideration:

The following answer can be used to effectively test abstract classes, without 
constructing a concrete implementation of them in mocks:

  public class AbstractMethodStub implements Answer<Object> {
    @Override
    public Object answer(InvocationOnMock invocation) throws Throwable {
      if (Modifier.isAbstract(invocation.getMethod().getModifiers())) {
        return Mockito.RETURNS_DEFAULTS.answer(invocation);
      }
      return Mockito.CALLS_REAL_METHODS.answer(invocation);
    }
  }

Then in the setUp() method one can use:

private AbstractFoo foo;

@Override
protected void setUp() {
  foo = Mockito.mock(AbstractFoo.class, new AbstractMethodStub());
}

I find this approach useful and would like if it was part of a standard set of 
answers, similarly to Mockito.CALL_REAL_METHODS, or Mockito.RETURN_DEFAULTS. 
Something like Mockito.STUB_ABSTRACT_METHODS or whatever name is appropriate. 

Original issue reported on code.google.com by bo.majew...@gmail.com on 27 Jan 2011 at 6:33

GoogleCodeExporter commented 8 years ago

Original comment by brice.du...@gmail.com on 11 Feb 2011 at 10:24

GoogleCodeExporter commented 8 years ago
Yeah, interesting idea. We'll look at it.

Cheers!

Original comment by szcze...@gmail.com on 15 Feb 2011 at 10:00

GoogleCodeExporter commented 8 years ago
I think mocking abstract classes is a great idea and it's a one-for-all 
solution to many problems such as providing partial mocks for fat interfaces, 
and for mocking async style API. For example:

abstract MockCreditCardService implements CreditCardService {

  @Override public void getCreditCard(String id, Callback<CreditCard> callback) {
    callback.onSuccess(getCreditCard(id));
  }

  // traditional non-callback variant for easy mocking
  abstract CreditCard getCreditCard(String id);
}

@Partial @Mock MockCreditCardService service;

when(service.getCreditCard("id1")).thenReturn(creditCard);

But I think Answer itself only gets half of the job done.

When users get to create abstract classes, they will want to use fields:

abstract class MockFoo {
  private final Bar bar = new Bar();

  ...
}

It'd be surprising if this bar field isn't initialized because Mockito doesn't 
call the constructor.

In fact, in our internal EasyMock-based testing infrastructure, we find the 
partial-mock for abstract class quite useful.

Another useful feature I'd hate to miss (we are internally discussing to move 
to Mockito because it's damn cool!), is the ability to create non-static inner 
classes. With inner class, the mock gets to access all the other mocks created 
by the test. For a class with non-default constructor, it may be the only type 
safe way for the test to pass in mocks. For example:

@Mock Request request;

abstract class MockPresenter extends Presenter {

  MockPresenter() {
    super(request);
  }

  ...
}

So, in general, I hope Mockito can support partial-mock-abstract-class in the 
core, with the default constructor called (and optionally suppressed).

Original comment by yuj...@gmail.com on 19 Apr 2011 at 1:53

GoogleCodeExporter commented 8 years ago
And this may be directly related to issue 114.

Regarding implementation, I haven't digged Mockito code enough. But EasyMock 
uses cglib Enhancer. It's not hard to pick a constructor when generating class 
with Enhancer. I suppose Mockito could do the same thing?

Original comment by yuj...@gmail.com on 19 Apr 2011 at 1:56

GoogleCodeExporter commented 8 years ago
It should be fairly easy. Objenesis creates instances and it configirable 
creation strategies.

Original comment by szcze...@gmail.com on 19 Apr 2011 at 6:55

GoogleCodeExporter commented 8 years ago
Here's a suggested API: we can have a @PartialMock annotation, or @Partial 
@Mock, when initMocks(this) is called, it scans the partial mocked fields and 
initialize them by invoking the parameterless constructor of the abstract class.

@PartialMock private MockCreditCardService creditCardService;

abstract class MockCreditCardService implements CreditCardService {
  private final Map<String, Callback<CreditCard>> callbacks = new HashMap<>();
  @Override public void getCreditCard(String id, Callback<CreditCard> callback) {
    calls.put(id, callback);
  }
}

Partial mocks should be able to handle non-static inner classes by injecting 
the enclosing test instance as the hidden outer "this" parameter. It allows the 
partials to use other mocked collaborators.

I hacked up an implementation using default answer but it would be nice if it's 
built into Mockito core (and if the idea sounds reasonable, I'll volunteer to 
send a patch)

Original comment by yuj...@gmail.com on 18 Dec 2012 at 5:22