WHERE TEST DOUBLES CAN LEAD YOU...
"With great power comes great responsibility". And Test Double Patterns gives you a great power. Your life is easier, tests lighter and faster.
But someday this power can turn against you.
Each day we are more comfortable with using Test Double Patters. We are creating assumptions. We are mocking services. We are ending with well covered code and tests... that tell us nothing.
That's why it's so important to recognize warning signs. To know the pitfalls that are waiting for us. To know what to do in these situations.
4. TD Types - Dummy Object
public class Blog
public void addArticle(Article article) ;
public int numberOfArticles() ;
@Test
public void shouldReturnNumberOfArticlesOnBlog() {
blog.addArticle(getDummyArticle());
blog.addArticle(getDummyArticle());
assertThat(blog.numberOfArticles(), is(2));
}
private Article getDummyArticle() {
return new Article();
}
5. TD Types – Fake Object
public class EventProcessor
private Store store;
public void add(Event event) { store.add(event); }
public void processAll() {
for (Event event : store.getAll()) {
event.process();
}
}
6. TD Types – Fake Object c.d.
public class FakeStore implements Store {
private final List<Event> events = new ArrayList<>();
@Override
public void add(Event event) { events.add(event); }
@Override
public List<Event> getAll() { return events; }
}
@Test
public void shouldProcessAll() {
Event event1 = spiedEvent();
Event event2 = spiedEvent();
store.add(event1);
store.add(event2);
processor.processAll();
verify(event1).process();
verify(event2).process();
}
8. TD Types – Spy Object
public class PublishedArticleEventTest {
@Spy private Article article;
@Test
public void shouldPublishArticle() {
PublishedArticleEvent event = new PublishedArticleEvent(article);
event.process();
verify(article).published();
}
}
public class PublishedArticleEvent implements Event {
private final Article article;
public PublishedArticleEvent(Article article) {
this.article = article;
}
public void process() {
article.published();
}
}
9. TD Types – Mock Object
@Test
public void shouldProcessOnlyThoseWhichSatisfiesPredicate() {
Event event1 = spiedEvent();
Event event2 = spiedEvent();
given(store.getAll()).willReturn(asList(event1, event2));
given(predicate.isSatisfiedBy(event1)).willReturn(true);
given(predicate.isSatisfiedBy(event2)).willReturn(false);
processor.processAll(predicate);
then(event1).should().process();
then(event2).should(never()).process();
}
// public class EventProcessor
public void processAll(EventPredicate predicate) {
for (Event event : store.getAll()) {
if (predicate.isSatisfiedBy(event))
event.process();
}
}
10. Pitfalls
• All we need are Unit Tests
• Unreal situation
• Aggregation over Composition
• Dependency -> Test Double
• More Mocks = More fragile Code
• Mocks return mocks return …
11. Make your life easier
• Say no to Accessors
• No additional behavior in Mocks
• No logic in constructors
• Object is lighter than mock
12. Use Parameter Object
public void foo(Bar bar, Baz baz, Xyz xyz) {
doSomething1(bar.getX(), baz.getZ());
doSomething2(bar.getX(), xyz.getY());
doSomething3(baz.getZ());
}
// IN TESTS
Z z = new Z();
Y y = new Y();
X x = new X();
Bar bar = new Bar(x);
Baz baz = new Baz(z);
Xyz xyz = new Xyz(y);
public void foo(FooContext context) {
doSomething1(context.getX(), context.getZ());
doSomething2(context.getX(), context.getY());
doSomething3(context.getZ());
}
// IN TESTS
Z z = new Z();
Y y = new Y();
X x = new X();
FooContext context = new FooContext(z, x, y);
13. Law of Demeter
// public class IsAdultPredicate {
public boolean apply(Person person) {
return person.getAge().getYears() >= ENOUGH;
}
@Test
public void shouldApplyWhenEnough() {
int enoughYears = 18;
Age age = mock(Age.class);
given(age.getYears()).willReturn(enoughYears);
Person person = mock(Person.class);
given(person.getAge()).willReturn(age);
}
// public class IsAdultPredicate {
public boolean apply(Person person) {
return person.getYears() >= ENOUGH;
}
@Test
public void shouldApplyWhenEnough() {
int enoughYears = 18;
Person person = mock(Person.class);
given(person.getYears()).willReturn(enoughYears);
}
14. Tell, don’t ask
// public class IsAdultPredicate {
public boolean apply(Person person) {
return person.isAdult();
}
@Test
public void shouldApplyWhenEnough() {
Person person = mock(Person.class);
given(person.isAdult()).willReturn(true);
}
15. Testing implementation
• "What" not "how" or "how" not "what"?
• Refactoring/Change is not safe
• Refactoring/Change affects unit tests,
not integration
• Implementation leaking
20. Boundary Object c.d.
public class ExternalReportingService {
public void publish(Report report, Priority priority);
public class ReportingService {
private ExternalReportingService reportingService;
public ReportingService(ExternalReportingService
reportingService) {
this.reportingService = reportingService;
}
public void publish(Report report) {
reportingService.publish(report, REGULAR);
}
}
21. Too many Test Double?
• How many is too many?
• Readability
• Warning sign
• Unit != Unit
• Violation of Law of Demeter
• Violation of Single Responsibility Principle