On Github benhyland / lightning
(high-speed!)
public void testCreditCardIsCharged() { paymentProcessor = new PaymentProcessor(mockCreditCardServer); when(mockCreditCardServer.isServerAvailable()).thenReturn(true); when(mockCreditCardServer.beginTransaction()).thenReturn(mockTransactionManager); when(mockTransactionManager.getTransaction()).thenReturn(transaction); when(mockCreditCardServer.pay(transaction, creditCard, 500)).thenReturn(mockPayment); when(mockPayment.isOverMaxBalance()).thenReturn(false); paymentProcessor.processPayment(creditCard, Money.dollars(500)); verify(mockCreditCardServer).pay(transaction, creditCard, 500); }
public void publishMessage(InternalMessage message, Session session, MessagePublisher publisher) { publisher.transformAndPublish(message, session); }
public void publishMessage(InternalMessage message, Session session, MessageRenderer renderer, MessagePublisher publisher) { RenderedMessage toPublish = renderer.transform(message); publisher.publish(toPublish, session); }
public Order acceptInstruction(Instruction instruction, Account account, PaymentProcessor processor) { if(account.canAfford(instruction.value())) { processor.debit(account.id(), instruction.value()); return Order.buildFrom(instruction); } return null; }
public Pair<Order, Charge> acceptInstruction(Instruction instruction, Account account) { if(account.canAfford(instruction.value())) { Charge debit = new Charge(account.id(), instruction.value()); Order order = Order.buildFrom(instruction); return Pair.of(order, debit); } return null; }
A unit doesn't necessarily mean a class.
If several classes are tightly coupled, it's OK to unit test them together.
If a class packages independent functions, it's OK to test them separately.
Reduce coupling by exposing effects in the interface between units.
Test the glue at integration or acceptance level.
Mocking is appropriate if you are testing an interaction which is genuinely an effect, at the edge of your component.
public void deliverMessage(Message message, Route route) { if(route.accept(message)) { auditLog.auditDeliverySuccess(message.id()); } else { auditLog.auditDeliveryFailure(message.id()); } }
Mock tests can sometimes be preferable to no tests, especially in legacy code while you aren't sure of all the requirements for a particular program.
Mocks, by their very nature, are coupled to mechanisms instead of outcomes. Mocks, or the setup code that builds them, have deep knowledge of the inner workings of several different classes.
That knowledge is the very definition of high-coupling.
- Bob Martin