So funktioniert mein Code. Ich habe ein Objekt, das den aktuellen Status einer Warenkorbbestellung darstellt und in einer Einkaufs-API eines Drittanbieters gespeichert ist. In meinem Controller-Code möchte ich Folgendes aufrufen können:
myOrder.updateQuantity(2);
Um die Nachricht tatsächlich an den Dritten zu senden, muss der Dritte auch einige Dinge wissen, die für DIESE Bestellung spezifisch sind, wie das orderIDund das loginID, die sich während der Lebensdauer der Anwendung nicht ändern.
Wenn ich also myOrderursprünglich erstelle , injiziere ich ein MessageFactory, das weiß loginID. Wenn updateQuantitydann gerufen wird, Ordergeht das weiter orderID. Der Steuercode ist einfach zu schreiben. Ein anderer Thread behandelt den Rückruf und aktualisiert, Orderwenn die Änderung erfolgreich war, oder informiert, Orderdass die Änderung fehlgeschlagen ist, wenn dies nicht der Fall war.
Das Problem ist das Testen. Da das OrderObjekt von a abhängt MessageFactoryund MessageFactorytatsächliche Messages zurückgeben muss (die es beispielsweise aufruft .setOrderID()), muss ich jetzt sehr komplizierte MessageFactoryMocks einrichten . Außerdem möchte ich keine Feen töten, da "Jedes Mal, wenn ein Mock einen Mock zurückgibt, stirbt eine Fee."
Wie kann ich dieses Problem lösen, während der Controller-Code genauso einfach bleibt? Ich habe diese Frage gelesen: /programming/791940/law-of-demeter-on-factory-pattern-and-dependency-injection, aber es hat nicht geholfen, weil es nicht über das Testproblem gesprochen hat .
Einige Lösungen, an die ich gedacht habe:
- Refaktorieren Sie den Code irgendwie so, dass die Factory-Methode keine realen Objekte zurückgibt. Vielleicht ist es weniger eine Fabrik und mehr ein
MessageSender? - Erstellen Sie eine Nur-Test-Implementierung von
MessageFactoryund fügen Sie diese ein.
Der Code ist ziemlich kompliziert, hier ist mein Versuch eines sscce:
public class Order implements UpdateHandler {
private final MessageFactory factory;
private final MessageLayer layer;
private OrderData data;
// Package private constructor, this should only be called by the OrderBuilder object.
Order(OrderBuilder builder, OrderData initial) {
this.factory = builder.getFactory();
this.layer = builder.getLayer();
this.data = original;
}
// Lots of methods like this
public String getItemID() {
return data.getItemID();
}
// Returns true if the message was placed in the outgoing network queue successfully. Doesn't block for receipt, though.
public boolean updateQuantity(int newQuantity) {
Message newMessage = factory.createOrderModification(messageInfo);
// *** THIS IS THE KEY LINE ***
// throws an NPE if factory is a mock.
newMessage.setQuantity(newQuantity);
return layer.send(newMessage);
}
// from interface UpdateHandler
// gets called asynchronously
@Override
public handleUpdate(OrderUpdate update) {
messageInfo.handleUpdate(update);
}
}
layer.senddie Nachricht gesendet wurde oder ob die richtige Nachricht gesendet wurde?
verify(messageMock).setQuantity(2), und verify(layer).send(messageMock);auch das updateQuantitysollte false zurückgeben, wenn das OrderUpdate bereits aussteht, aber ich habe diesen Code aus sscce-Gründen weggelassen.
OrderObjekt und das Objekt geschrieben habenMessageFactory. Dies ist eine gute Beschreibung, aber es ist etwas abstrakt, direkt mit einer klaren Antwort zu sprechen.