Unit tests are awesome! They are small, they run fast, and they require little maintenance. Most importantly, unit tests run your code in isolation and thereby provide feedback on its design. However, what if you cannot get your code under test? Your design obviously needs improvement, but what if you cannot change that? You write an integration test, or you look into Mocking.
Mocking has a bad reputation, but it can come in handy and even save your day. In this workshop, you’ll use *Mockito* to unit test a small Java program with mocks, stubs, and spies. You’ll apply advanced mocking techniques to cope with complicated designs, and use matchers for less coupling to implementation details. You’ll learn about different types of dependencies and how mocks help to run them against assertions or even with behaviour verification. In the end, we’ll review common myths about mocking and discuss your new experience with a cold beer. Mock responsibly!
5. We don’t like mocks
Mocking is fundamentally evil. It encourage you to write bad, poorly
factored code, not-very-functional code. It encourages you to avoid
standing up as much of your system as possible during integration testing.
Finally, all too often at the end of all that mocking all you end up with are some
really well tested Mocks nested N! levels. In other words, mocking produces
a bunch of useless code that costs money and time to maintain.
Mocking is Evil
— Eric Merritt { http://blog.ericbmerritt.com }
“
“”
6. We don’t like mocks
They had written a series of tests for the business logic layer that completely
mocked out the data access layer. Normally this is fine as you don’t want to
actually hit a database in a unit test, but what they had done was actually
mock out the majority of their business logic which meant the tests
were not doing anything useful at all.
All Your Mocks are Evil!!
— Stephen Haunts { https://stephenhaunts.com }
“
“”
7. We don’t like mocks
If I could just mock out all of the EJB stuff, I'd be sitting pretty. […]
Not even an hour had passed and I needed to mock
java.sql.Connection. 40 methods! Getters and setters for every parameter,
return value and counter for every method? .... hmmmm .... thinking this
through a little .... the reason we make the attributes private is for the
sake of encapsulation - to hide how things are done on the inside so that
we can change our business logic later without breaking a bunch of stuff that
decided to tap into our innards. But that doesn't really apply to a mock,
does it?
Evil Unit Testing
— Paul Wheaton { http://www.javaranch.com }
“
“”
8. We don’t like mocks
I’ve never been a big fan of mocking frameworks, for one simple reason.
Simple stubbing relies on assumptions on the functioning of the mocked
part of your system that rarely match reality, for no one is going to look
at all the documentation of an API and decompile the code when writing a
one-line stub.
Mocking considered evil
— Sebastien Lambla { https://serialseb.com/blog/ }
“
“”
13. Key: Test in Isolation.
Simple: Arrange. Act. Assert.
@Test
public void setPropertiesWithValidPropertiesYieldsSameObject()
throws Exception {
Properties expected = new Properties();
Item testObj = new Item("/path").setProperties(expected);
assertSame(expected, testObj.getProperties());
}
14. @Test
public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception {
ResourceResolver resolver = new ResourceResolverImpl(client);
createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate());
createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate());
createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate());
createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate());
Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content");
createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties());
Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage");
Properties imageProps = new Properties()
.append("source", "<sourceValue>")
.append("caption", "titleValue")
.append("sling:resourceType", ComponentType.IMAGE)
.append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT))
.append(JCR_LAST_MODIFIED_BY, "admin");
createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps);
TextImageController testObj = new TextImageController().setup(createContext(target, contentPage));
assertEquals("titleValue<br /><sourceValue>", testObj.getImageLegend());
}
Key: Test in Isolation.
15. @Test
public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception {
ResourceResolver resolver = new ResourceResolverImpl(client);
createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate());
createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate());
createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate());
createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate());
Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content");
createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties());
Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage");
Properties imageProps = new Properties()
.append("source", "<sourceValue>")
.append("caption", "titleValue")
.append("sling:resourceType", ComponentType.IMAGE)
.append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT))
.append(JCR_LAST_MODIFIED_BY, "admin");
createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps);
TextImageController testObj = new TextImageController().setup(createContext(target, contentPage));
assertEquals("titleValue<br /><sourceValue>", testObj.getImageLegend());
}
Arrange
Key: Test in Isolation.
16. @Test
public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception {
ResourceResolver resolver = new ResourceResolverImpl(client);
createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate());
createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate());
createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate());
createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate());
Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content");
createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties());
Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage");
Properties imageProps = new Properties()
.append("source", "<sourceValue>")
.append("caption", "titleValue")
.append("sling:resourceType", ComponentType.IMAGE)
.append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT))
.append(JCR_LAST_MODIFIED_BY, "admin");
createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps);
TextImageController testObj = new TextImageController().setup(createContext(target, contentPage));
assertEquals("titleValue<br /><sourceValue>", testObj.getImageLegend());
}
Arrange
Act
Key: Test in Isolation.
17. @Test
public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception {
ResourceResolver resolver = new ResourceResolverImpl(client);
createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate());
createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate());
createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate());
createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate());
Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content");
createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties());
Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage");
Properties imageProps = new Properties()
.append("source", "<sourceValue>")
.append("caption", "titleValue")
.append("sling:resourceType", ComponentType.IMAGE)
.append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT))
.append(JCR_LAST_MODIFIED_BY, "admin");
createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps);
TextImageController testObj = new TextImageController().setup(createContext(target, contentPage));
assertEquals("titleValue<br /><sourceValue>", testObj.getImageLegend());
}
Arrange
Act
Assert
Key: Test in Isolation.
18. Dependencies vs. Isolation
Sandbox
Test Object
{state, behavior}
Direct
Collaborator
{state, behavior}
Indirect
Collaborator
{state, behavior}
Parent
{state, behavior}
Data
{state}
Event
{state,
behavior}
19. Arrange: Dependencies?
• What: State, Behavior[, Event and Data]
• Where: Local, Parent, External
• How:
• Explicit/Implicit: Is it injectable?
• Direct/Indirect: Do I depend on it directly?
• Accessible/Inaccessible: Is it static, final etc.?
20. Mockito 101
• Mock: Empty Object: No state, No behavior.
ItemService service = mock(ItemService.class);
• Stub: Specify Behavior
when(service.load(“index.html”)).thenReturn(“foobar”);
• Spy: Real Object: Real / partial state and behavior
ItemService service = spy(new ItemService());
doReturn(“foobar”).when(service).load(“index.html”);
31. Write valuable tests.
• Go through the code add unit tests
• Think about what to mock and why?
• Where to start? Follow the hints in the code!
• Remember: You are allowed to change the code!
33. Mocks, revisited
• Can Mocking cause poorly factored code?
• Need for mocks: design feedback
• Be aware deeply nested mocks!
• Can Mocking break encapsulation?
• Mocked internal state
• Need for behavior verification
• Can Mocking create artificial behavior?
• Mocks with state are dangerous
• Use stubs with canned values
• Can Mocking reduce test value?
• Mock direct collaborators only
• Under-specify with matchers
34. No Hangover, please!
Rules of the road:
• Try to use the real thing
• Mocks are for direct collaborators
• Never mock the test object
• Be aware of deeply-nested mocks, e.g.
RETURN_DEEP_STUBS
• Stateless mocks, with stubs to return
canned values
• Don’t verify on non public API
• Under-specify in mocks and stubs with
matchers.
35. Ready. Set. Go.
• Unit Testing In The Real World
• More guidance. More
examples.
• How to write unit tests?
• Measure test coverage.