Skip to content

Instantly share code, notes, and snippets.

@muller
Last active September 9, 2016 15:13
Show Gist options
  • Save muller/c667c2fe7aa7293266b246620bd5e93b to your computer and use it in GitHub Desktop.
Save muller/c667c2fe7aa7293266b246620bd5e93b to your computer and use it in GitHub Desktop.
values vs external interaction

Values vs External interactions

If I was to define the dependency arrow in UML 3.0 I would draw it in red with a pitchfork. It is evil.

External dependencies like databases or webservices will slow down your team. The development environment will get complex with installations, configurations and debugging.

Mocking isn't the only answer. Working with values only, allow you to implement a slice of your application in isolation.

Dependencies and rules

If you look at the example in why.groovy you might think it's strange. So, why you do it in your code?

void disposeOrders(Long userId) {
  List<Order> orders = repository.findAllByUserId(userId);
  for (Order order : orders) {
    if(order.getTotal() > DISPOSE_THRESHOLD) {
      repository.remove(order);
    }
  }
}

If the code above look familiar, be careful. In this code there is a mix of dependencies (repository.findAllByUserId and repository.remove) and rules (order.getTotal() > DISPOSE_THRESHOLD) that may degrade maintainability.

To test this code you will need the real repository (integration test) or mocks:

when(repository.findAllByUserId(1)).thenReturn(sample);
disposeOrders("1");
verify(repository).remove(sample.get(2));
verify(repository).remove(sample.get(7));

Basically it needs a lot of setup to test a single IF. This test might not look too bad at first, but in a larger scale with more dependencies it will end up being a mock festival.

To avoid this I'd suggest to separate dependencies and rules:

class DisposableOrders {
  static Stream<Order> selectDisposable(List<Order> orders) {
    return orders.stream().filter(order -> order.getTotal() > DISPOSE_THRESHOLD);
  }
}
class OrderManager {
  void disposeOrders(Long userId) {
    selectDisposable(repository.findAllByUserId(userId)).forEach(order -> repository.remove(order));
  }
}

Now the class DisposableOrders has the rules and OrderManager is responsible for integration. And the test should be simpler:

assertThat(selectDisposable(sampleOrders), contains(sample.get(2), sample.get(7)));

If you like you can write a integration test for OrderManager or just don't cover it considering its simplicity.

So, what?

One way or the other you will need to integrate your software and fight with availability and configuration of external resources. As much as you can defer it as better, so if you have the chance to implement you core slice with the correct separation of concerns go for it.

References

https://www.destroyallsoftware.com/talks/boundaries

interface Source {
int a()
int b()
}
class Sum {
def src
int sum() { src.a() + src.b() }
}
def mock = new groovy.mock.interceptor.MockFor(Source)
mock.demand.a { 1 }
mock.demand.b { 1 }
assert new Sum(src: mock.proxyDelegateInstance()).sum() == 2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment