1

I'm running through some service tests and I am testing a concrete class that extends from one that uses generics.

An example setup of the service layer is below:

public abstract class AbstractService <E extends AbstractEntity, IT extends AbstractItem> {

    public void deleteAllItems(E entity) {
        List<IT> items = new ArrayList<IT>(entity.getItems());
        for(IT item : items) {
            //Yada, yada
        }
    }
}

public class Service extends AbstractService<Entity, Item> {

}

public class OtherService() {
    @Inject
    private ServiceManager serviceManager;

    public void deleteItems(Entity e) {
        serviceManager.getService().deleteAllItems(e);
    }
}

Then to test it I have the following:

public class Test {
    private Service service;
    private OtherService otherService;
    private ServiceManager serviceManager;

    @BeforeMethod
    public void setup() {
        serviceManager= mock(serviceManager.class);
        service= mock(Service.class);
        when(serviceManager.getService()).thenReturn(service);
        otherService=injector.getInstance(OtherService.class);
    }

    @Test
    public void test() {
        Entity e = new Entity();
        //Attach some items
        otherService.deleteItems(e);
        verify(service).deleteAllItems(e);
    }
}

This should call the OtherService, which exists (We're using injection to get ahold of the object), and then call the method deleteItems(), which in turn should call deleteAllItems() on the Service. Before I had implemented the Java generics, this worked fine, but since I have implemented the Java generics, the Mockito test fails with the following exception:

java.lang.NoSuchMethodError: Service.deleteAllItems(Entity;)V at Test.test(Test.java:XXX) org.mockito.exceptions.misusing.UnfinishedVerificationException: Missing method call for verify(mock) here: -> at Test.test(Test.java:XXX)

Example of correct verification: verify(mock).doSomething()

Also, this error might show up because you verify either of: final/private/equals()/hashCode() methods. Those methods cannot be stubbed/verified.

Which sounds like it can't find the method. Should I instead mock the abstract class of AbstractService or is there something else that I am missing?

EDIT

From what I've seen of the Mockito inner workings, it creates an instance of this:

public void AbstractService.deleteAllItems(Entity)

For the MockitoMethod object, so that would make sense that Service.deleteAllItems() "isn't called", it appears Mockito assumes only the baseclass was ever called. So it does appear that I need to mock the base class instead. I'm going to investigate further, but if anyone has any other ideas, I'm open to suggestions

Draken
  • 3,134
  • 13
  • 34
  • 54
  • For unit tests I suggest not using the `Injector`. Use a package-private constructor to create an instance of the class under test. – NamshubWriter Jun 13 '16 at 14:03
  • Any specific reason for this @NamshubWriter? We're using Injection to control the instances of the objects that are used and it works OK for us. It's simple enough to bind the mocked objects to the Injection and then allow the `Injector` to do the work of inserting the correct class. We've only had an issue on this one variation. Also, how do you solve multiple layers of injection? There can be a lot of mocking rules written if you're not careful, with injection we can set it at one location and the other objects will use those instances. – Draken Jun 13 '16 at 14:20
  • dependency injection is great for your production server/binary and for large integration tests. For unit tests it makes your tests slow, fragile, and hard to understand. It can also hide code smells (like classes with too many dependencies). Tests should follow KISS – NamshubWriter Jun 13 '16 at 14:28

3 Answers3

1

I can suggest to localize the problem - either it is in mocking:

@Test
public void test() {
    Entity e = new Entity();
    service.deleteItems(e); // Note! 'service' itself, not an 'otherService'
    verify(service).deleteAllItems(e);
}

or in injection (remove inheritance and generics):

public class Service /*extends AbstractService<Entity, Item>*/ {
    public void deleteAllItems(Entity entity) {
        //...
    }
}

Split the problem iterativelly and you will find the cause.

ursa
  • 4,404
  • 1
  • 24
  • 38
  • We already had it working before without the Abstracts, so I'm aware it was working before without the use of generics. However, we now need it to be working with abstracts and generics, as that it what we're working towards, I've got further information, but will post that when I'm in the office on Monday – Draken Jun 12 '16 at 11:46
0

When you create a non-generic subclass of a generic class, Java creates "bridge methods" for any methods that use the generic type. The bridge methods look like the inherited methods but use the the specific class specified for the generic parameters instead of generics.

Java creates these methods because the methods of the subclass are not generic, so they need to "look like" non-generic methods (i.e. not subject to erasure, reflection will work as expected, etc). See this answer for details.

The solution is to have Mockito mock the type returned by serviceManager.getService().

Community
  • 1
  • 1
NamshubWriter
  • 23,549
  • 2
  • 41
  • 59
  • You mean like in these lines: `serviceManager= mock(serviceManager.class); service= mock(Service.class); when(serviceManager.getService()).thenReturn(service);` Or do you mean to mock a different type? – Draken Jun 13 '16 at 08:09
  • @Draken you didn't show the signature of `getService()` so I can't say which type you should mock – NamshubWriter Jun 13 '16 at 14:00
  • `getService()` only returns a type of `Service` – Draken Jun 13 '16 at 14:11
  • @Draken so the signature of `ServiceManager.g etService()` is "Service getService()"? What type is being injected into `ServiceManager`? – NamshubWriter Jun 13 '16 at 14:22
  • What type of Service? `Service` again. As for the mocking, that's done with: `service= mock(Service.class); when(serviceManager.getService()).thenReturn(service);` There's a separate area for why we have the generics and abstract classes. We're currently going through a major code refactoring, so not everything has been implemented to be using abstract objects, where required – Draken Jun 13 '16 at 14:26
0

After further investigation, I found a way to force Mockito to call the correct class. As I mentioned briefly, we're using injection to get ahold of the object. During the setup we do run through a setup of the injector, which I hadn't felt was causing the issue. But it did present a solution. This was how we were calling it:

Injector injector = Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                service = mock(Service.class);
                binder.bind(Service.class).
                    toInstance(service);
}

To solve the issue, we just bound the AbstractService class to the mocked instance of the Service class, like so:

Injector injector = Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                service = mock(Service.class);
                binder.bind(Service.class).
                    toInstance(service);
                binder.bind(AbstractService.class).
                    toInstance(service);                    
}

So now, when Mockito attempts to get an instance of the AbstractService, it calls the mocked Service and solves our issue.

If anyone has any feedback it there is an alternative solution, then feel free to post and I can test it out and check if there are better methods that what we are doing.

Draken
  • 3,134
  • 13
  • 34
  • 54
  • What fails if you remove the binding to Service.class? I am beginning to suspect you have some classes with constructors using `AbstractService` and others use `Service` – NamshubWriter Jun 13 '16 at 14:33
  • @NamshubWriter We have no constructors currently using `AbstractService`, the only time it is currently used is via imports, the single fix I have implemented above and in extension for the `Service` class. We've only just started refactoring the code, so the `AbstractService` is only used by `Service`. The `NewService` will come later. Removing the binding to `Service` and leaving just `AbstractService` works funnily enough. I'd assume that's because the other methods available only in `Service` are currently not under test and Mockito works fine using an instance of `AbstractService` – Draken Jun 13 '16 at 14:42
  • If it works without the binding to `Service` then the injected classes are taking in `AbstactService`. – NamshubWriter Jun 13 '16 at 14:49
  • As to why they are doing that, I don't have a clue. As I said, it's not being injected anywhere, only the concrete class. – Draken Jun 13 '16 at 14:53