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 orderID
und das loginID
, die sich während der Lebensdauer der Anwendung nicht ändern.
Wenn ich also myOrder
ursprünglich erstelle , injiziere ich ein MessageFactory
, das weiß loginID
. Wenn updateQuantity
dann gerufen wird, Order
geht das weiter orderID
. Der Steuercode ist einfach zu schreiben. Ein anderer Thread behandelt den Rückruf und aktualisiert, Order
wenn die Änderung erfolgreich war, oder informiert, Order
dass die Änderung fehlgeschlagen ist, wenn dies nicht der Fall war.
Das Problem ist das Testen. Da das Order
Objekt von a abhängt MessageFactory
und MessageFactory
tatsächliche Message
s zurückgeben muss (die es beispielsweise aufruft .setOrderID()
), muss ich jetzt sehr komplizierte MessageFactory
Mocks 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
MessageFactory
und 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.send
die Nachricht gesendet wurde oder ob die richtige Nachricht gesendet wurde?
verify(messageMock).setQuantity(2)
, und verify(layer).send(messageMock);
auch das updateQuantity
sollte false zurückgeben, wenn das Order
Update bereits aussteht, aber ich habe diesen Code aus sscce-Gründen weggelassen.
Order
Objekt und das Objekt geschrieben habenMessageFactory
. Dies ist eine gute Beschreibung, aber es ist etwas abstrakt, direkt mit einer klaren Antwort zu sprechen.