2010-10-15 24 views
4

Je suis encore en train d'apprendre mockito et en ce moment j'apprends comment injecter des mocks.Mockito: injecter des mocks tout au long du flux de contrôle

J'ai un objet en test avec une méthode particulière qui dépend d'autres objets. Ces objets, à leur tour, dépendent d'autres objets. Je veux me moquer de certaines choses et faire en sorte que ces simulacres soient utilisés partout pendant l'exécution - tout au long du flux de contrôle de la méthode.

Par exemple suppose qu'il ya des classes comme:

public class GroceryStore { 
    public double inventoryValue = 0.0; 
    private shelf = new Shelf(5); 
    public void takeInventory() { 
     for(Item item : shelf) { 
      inventoryValue += item.price(); 
     } 
    } 
} 

public class Shelf extends ArrayList<Item> { 
    private ProductManager manager = new ProductManager(); 
    public Shelf(int aisleNumber){ 
     super(manager.getShelfContents(aisleNumber); 
    } 
} 

public class ProductManager { 
    private Apple apple; 
    public void setApple(Apple newApple) { 
     apple = newApple; 
    } 
    public Collection<Item> getShelfContents(int aisleNumber) { 
     return Arrays.asList(apple, apple, apple, apple, apple); 
    } 
} 

J'ai besoin d'écrire du code de test avec des portions le long des lignes de:

.... 
@Mock 
private Apple apple; 
... 
when(apple.price()).thenReturn(10.0); 
... 

... 
@InjectMocks 
private GroceryStore store = new GroceryStore(); 
... 
@Test 
public void testTakeInventory() { 
    store.takeInventory(); 
    assertEquals(50.0, store.inventoryValue); 
} 

Chaque fois que apple.price() est appelée, je veux ma fausse pomme pour être celle utilisée. Est-ce possible?

EDIT:
Important ...
la classe qui contient l'objet que je veux railler a un setter pour cet objet. Cependant, je n'ai pas vraiment de contrôle sur cette classe au niveau que je suis en train de tester. Donc, suivant l'exemple, bien que ProductManager ait un setter pour Apple, je n'ai pas le moyen d'obtenir le ProductManager de l'objet GroceryStore.

+0

Je pense que vous devez créer une usine pour Apple et maquette l'usine –

+0

@Alois: quelque chose le long de ces lignes peut-être raison mais. . . Comment est-ce que j'obtiens ProductManager pour utiliser l'usine (à partir de mon test d'unité de GroceryStore)? – gMale

+0

avec un setter dans ProductManager pour définir l'usine. Utilisez-vous un cadre DI (injection de dépendance)? ressort ou guice par exemple –

Répondre

2

Le problème est que vous créez des objets dont vous dépendez en appelant new au lieu de l'injecter. Injecter ProductManager dans Shelf (par exemple dans le constructeur), et injecter Shelf dans GroceryStore. Ensuite, en test, utilisez des mock. Si vous souhaitez utiliser @InjectMocks, vous devez injecter par des méthodes de définition.

Par constructeur, il pourrait ressembler à ceci:

public class GroceryStore { 
    public double inventoryValue = 0.0; 
    private shelf; 

    public GroceryStore(Shelf shelf) { 
    this.shelf = shelf; 
    } 

    public void takeInventory() { 
    for(Item item : shelf) { 
     inventoryValue += item.price(); 
    } 
    } 
} 

public class Shelf extends ArrayList<Item> { 
    private ProductManager manager; 

    public Shelf(int aisleNumber, ProductManager manager) { 
    super(manager.getShelfContents(aisleNumber); 
    this.manager = manager; 
    } 
} 

public class ProductManager { 
    private Apple apple; 
    public void setApple(Apple newApple) { 
    apple = newApple; 
    } 
    public Collection<Item> getShelfContents(int aisleNumber) { 
    return Arrays.asList(apple, apple, apple, apple, apple); 
    } 
} 

Ensuite, vous pouvez le tester moqueur tous les objets dont vous dépendez:

@Mock 
private Apple apple; 
... 
when(apple.price()).thenReturn(10.0); 

@InjectMocks 
private ProductManager manager = new ProductManager(); 

private Shelf shelf = new Shelf(5, manager); 
private GroceryStore store = new GroceryStore(shelf); 

//Then you can test your store. 
+0

J'ai oublié que j'avais cette question ouverte! :) La réponse de base s'est avérée être "Non, vous ne pouvez pas injecter des simulacres tout le long du flux de contrôle d'un appel de méthode." Cela signifie que vous ne pouvez pas créer un simulacre et l'appliquer partout, automatiquement. Vous devez l'installer manuellement où vous le voulez, pour ainsi dire. Il serait beaucoup plus pratique d'exiger, "partout où vous voyez une pomme, remplacez-la par mon faux!" Mais cela ne peut pas être fait. Vous avez raison, la seule solution est de changer le code et d'injecter les mock, "manuellement". Merci de prendre le temps de répondre. – gMale

+0

Vous êtes les bienvenus. Il est généralement recommandé d'injecter des dépendances au lieu de les créer par "nouveau". Cela rend votre code plus testable. Misko Hevery a un bon guide à ce sujet: http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/ – amorfis

+0

@gmale: En fait, vous pouvez le faire (c.-à-d., Se moquer de toutes les instances d'une classe donnée, indépendamment de qui les crée, où et quand), mais cela nécessite un outil moqueur plus performant, tel que JMockit (le mien) ou PowerMockito. –