Here's how my code works. I have an object that represents the current state of something akin to a shopping cart order, stored in a 3rd party shopping API. In my controller code, I want to be able to call:
myOrder.updateQuantity(2);
In order to actually send the message to the third party, the third party also needs to know several things that are specific to THIS order, like the orderID
, and the loginID
, which will not change in the lifetime of the application.
So when I create myOrder
originally, I inject a MessageFactory
, which knows loginID
. Then, when updateQuantity
is called, the Order
passes along orderID
. The controlling code is easy to write. Another thread handles the callback and updates Order
if its change was successful, or informs Order
that its change failed if it was not.
The problem is testing. Because the Order
object depends on a MessageFactory
, and it needs MessageFactory
to return actual Message
s (that it calls .setOrderID()
on, for example), now I have to set up very complicated MessageFactory
mocks. Additionally, I don't want to kill any fairies, as "Every time a Mock returns a Mock a fairy dies."
How can I solve this problem while keeping the controller code just as simple? I read this question: https://stackoverflow.com/questions/791940/law-of-demeter-on-factory-pattern-and-dependency-injection but it didn't help because it didn't talk about the testing problem.
A few solutions I've thought of:
- Somehow refactor the code to not require that the factory method return real objects. Perhaps it's less of a factory and more of a
MessageSender
? - Create a testing-only implementation of
MessageFactory
, and inject that.
The code is pretty involved, here's my attempt at an 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);
}
}
Order
object and theMessageFactory
. This is a good description, but it's a bit abstract to address directly with a clear answer.layer.send
sends the message, or that it sends the correct message?verify(messageMock).setQuantity(2)
, andverify(layer).send(messageMock);
Also the theupdateQuantity
should return false if theOrder
already has a pending update, but I omitted that code for sscce reasons.