2016-06-10 5 views
1

Ich führe einige Diensttests durch und ich teste eine konkrete Klasse, die sich von einem erstreckt, der Generics verwendet.Mockito schlägt auf NoSuchMethodError fehl, da die Methode in der abstrakten Klasse ist, die generische Methode verwendet

Ein Beispiel Aufbau der Service-Schicht ist unten:

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); 
    } 
} 

Dann ist es zu testen ich folgendes haben:

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); 
    } 
} 

Dies sollte die OtherService nennen, die (Wir existiert verwenden Injektion, um das Objekt zu erhalten), und rufen Sie dann die Methode deleteItems(), die wiederum deleteAllItems() auf der Service aufrufen sollte. Bevor ich das Java-Generika umgesetzt hatte, das hat gut funktioniert, aber da ich das Java-Generika umgesetzt habe, schlägt der Mockito Test mit der folgenden Ausnahme:

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.

Welche klingt es nicht die Methode finden kann. Sollte ich stattdessen die abstrakte Klasse von AbstractService verspotten oder gibt es noch etwas, das mir fehlt?

EDIT

Von dem, was ich habe die Mockito Innenleben zu sehen ist, erzeugt es ein Beispiel dafür:

public void AbstractService.deleteAllItems(Entity) 

Für das MockitoMethod Objekt, so dass Sinn, dass Service.deleteAllItems() machen würde " wird nicht aufgerufen ", es scheint, Mockito nimmt an, dass nur die Basisklasse jemals aufgerufen wurde. So scheint es, dass ich stattdessen die Basisklasse verspotten muss. Ich werde weiter untersuchen, aber wenn jemand noch andere Ideen hat, ich bin offen für Vorschläge

+0

Bei Gerätetests schlage ich vor, den 'Injektor' nicht zu verwenden. Verwenden Sie einen package-private-Konstruktor, um eine Instanz der zu testenden Klasse zu erstellen. – NamshubWriter

+0

Gibt es einen bestimmten Grund für diesen @NamshubWriter? Wir verwenden Injection, um die Instanzen der verwendeten Objekte zu steuern, und es funktioniert für uns. Es ist einfach genug, die verspotteten Objekte an die Injektion zu binden und dann dem "Injektor" zu erlauben, die korrekte Klasse einzufügen. Wir hatten nur ein Problem mit dieser einen Variante. Auch, wie lösen Sie mehrere Schichten der Injektion? Es kann eine Menge Spottregeln geben, wenn Sie nicht vorsichtig sind, mit der Injektion können wir es an einer Stelle setzen und die anderen Objekte werden diese Instanzen benutzen. – Draken

+0

Abhängigkeitsinjektion ist ideal für Ihre Produktionsserver/Binär-und für große Integrationstests. Bei Komponententests werden Ihre Tests verlangsamt, sind zerbrechlich und schwer zu verstehen. Es kann auch Code-Gerüche verbergen (wie Klassen mit zu vielen Abhängigkeiten). Tests sollten folgen KISS – NamshubWriter

Antwort

0

Nach weiteren Untersuchungen fand ich einen Weg, um Mockito zu zwingen, die richtige Klasse zu nennen. Wie ich kurz erwähnt habe, verwenden wir Injektion, um das Objekt zu erhalten. Während des Setups durchlaufen wir einen Setup des Injektors, von dem ich nicht das Gefühl hatte, dass er das Problem verursacht. Aber es hat eine Lösung präsentiert.Das war, wie wir es nannten:

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

Um das Problem zu lösen, wir gebunden nur die AbstractService Klasse zur verspotteten Instanz der Service Klasse, etwa 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, jetzt, wenn Mockito versucht, eine Instanz des AbstractService zu bekommen, ruft es das gespottete Service auf und löst unser Problem.

Wenn jemand Feedback hat, dann gibt es eine alternative Lösung, dann zögern Sie nicht zu posten und ich kann es testen und prüfen, ob es bessere Methoden gibt, was wir tun.

+0

Was schlägt fehl, wenn Sie die Bindung an Service.class entfernen? Ich beginne zu vermuten, dass Sie einige Klassen mit Konstruktoren haben, die 'AbstractService' verwenden und andere' Service' verwenden. – NamshubWriter

+0

@NamshubWriter Wir haben keine Konstruktoren, die 'AbstractService' verwenden, die einzige Zeit, die es momentan verwendet wird, ist über Importe, die einzige Lösung, die ich habe oben und in Erweiterung für die 'Service' Klasse implementiert. Wir haben gerade erst damit begonnen, den Code zu refaktorieren, daher wird der 'AbstractService' nur von' Service' verwendet. Der 'NewService' wird später kommen. Das Entfernen der Bindung zu "Service" und das Verlassen von "AbstractService" funktioniert komischerweise. Ich nehme an, dass die anderen Methoden, die nur in 'Service' verfügbar sind, derzeit nicht getestet werden und Mockito funktioniert gut mit einer Instanz von 'AbstractService' – Draken

+0

Wenn es ohne die Bindung an' Service' funktioniert, nehmen die injizierten Klassen 'auf AbstactService'. – NamshubWriter

1

ich vorschlagen kann, um das Problem zu lokalisieren - entweder ist es in spöttisch:

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

oder in Injektion (Vererbung und Generics entfernen):

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

Teilen Sie das Problem iterativly und Sie werden die Ursache finden.

+0

Wir hatten bereits funktioniert ohne die Abstracts, so dass ich weiß, dass es vorher ohne die Verwendung von Generika funktioniert. Jetzt brauchen wir es jedoch, um mit Abstracts und Generika zu arbeiten, weil es das ist, woran wir arbeiten, ich habe weitere Informationen, aber werde das posten, wenn ich am Montag im Büro bin – Draken

0

Wenn Sie eine nicht generische Unterklasse einer generischen Klasse erstellen, erstellt Java "bridge methods" für alle Methoden, die den generischen Typ verwenden. Die Bridge-Methoden sehen aus wie die geerbten Methoden, verwenden aber die spezifische Klasse, die für generische Parameter anstelle von Generika angegeben ist.

Java erstellt diese Methoden, weil die Methoden der Unterklasse nicht generisch sind, also müssen sie wie "nicht-generische" Methoden aussehen (d. H. Nicht löschbar, Reflektion wird wie erwartet funktionieren, usw.). Einzelheiten finden Sie unter this answer.

Die Lösung ist, dass Mockito den von serviceManager.getService() zurückgegebenen Typ vortäuscht.

+0

Du meinst wie in diesen Zeilen: 'serviceManager = mock (serviceManager.class); service = mock (Serviceklasse); wenn (serviceManager.getService()). ThenReturn (service); ' Oder meinst du, einen anderen Typ zu verspotten? – Draken

+0

@Draken Sie haben nicht die Signatur von 'getService()' angezeigt, also kann ich nicht sagen, welchen Typ Sie spotten sollten – NamshubWriter

+0

'getService()' gibt nur einen Typ von 'Service' zurück – Draken

Verwandte Themen