Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Mocking without the Hangover

535 views

Published on

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!

Published in: Software
  • Be the first to comment

Mocking without the Hangover

  1. 1. Mocking without the Hangover Jan Wloka jan.wloka@quatico.com @crashtester
  2. 2. Small Engineering Shop in Zurich
  3. 3. Small Engineering Shop in Zurich http:// jobs.quatico.com
  4. 4. Mocks, seriously?!
  5. 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. 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. 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. 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/ } “ “”
  9. 9. I’ve been there… ~10’000 Unit tests ~80% coverage ø refactoring possible
  10. 10. Let’s talk! about your experience
  11. 11. Why mocking?
  12. 12. Why mocking? Unit testing at scale
  13. 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. 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 />&lt;sourceValue&gt;", testObj.getImageLegend()); } Key: Test in Isolation.
  15. 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 />&lt;sourceValue&gt;", testObj.getImageLegend()); } Arrange Key: Test in Isolation.
  16. 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 />&lt;sourceValue&gt;", testObj.getImageLegend()); } Arrange Act Key: Test in Isolation.
  17. 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 />&lt;sourceValue&gt;", testObj.getImageLegend()); } Arrange Act Assert Key: Test in Isolation.
  18. 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. 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. 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”);
  21. 21. Partial Mocking • Gradually mock behaviour • mock(FooBar.class) • mock(FooBar.class, CALLS_REAL_METHODS) • spy(new FooBar())
  22. 22. Default Answers public IProjectLink aProjectLink(String label, String linkType) { IProjectLink result= mock(IProjectLink.class, RETURNS_SMART_NULLS); when(result.getLabel()).thenReturn(label); when(result.getLinkType()).thenReturn(linkType); return result; } • Other default answers: • CALLS_REAL_METHODS (partial mocking) • RETURNS_MOCKS (mock return values) • RETURNS_DEEP_STUBS (stub transitively) • RETURNS_DEFAULTS (default answer)
  23. 23. Stub it in or out? when(mock.methodCall(Matcher.anyString())).thenReturn(“value”); when(mock.methodCall(Matcher.anyString())).then(new Answer() {}); when(mock.methodCall(Matcher.anyString())).thenThrow(Exception.class); doReturn(“value”).when(mock).methodCall(Matcher.anyString()); doAnswer(new Answer() {}).when(mock).methodCall(Matcher.anyString()); doThrow(Exception.class).when(mock).methodCall(Matcher.anyString()); obj method argument matchers value / answer obj method argument matchersvalue / answer
  24. 24. Danger: Partial Mocks public String load(String path) throws FooException { // ... // method behavior //... return result; } doReturn().when() ——> when().thenReturn() ——> public class DbTest {
 @Mock
 private IPropertiesStore mockedStore;
 @InjectMocks
 private DB testObj;
 @Before
 public void setUp() throws Exception {
 testObj = DB.init();
 MockitoAnnotations.initMocks(this);
 }
 InjectBehavior InjectState
  25. 25. Assert: Effects? • Assert genuine effects: • Returned values. Changed state. Exceptions.
 
 assertEquals(), assertNotNull(), @Test(expect = …) • Mocks/spies verify behavior: • Called methods. Count calls. Capture arguments. 
 
 verify(mock,times(2)).methodFoo(Matchers.anyString())
  26. 26. Verify: calls w/ args @Test public void getContentShouldManageInputStreamsProperly() throws Exception { InputStream inputStream= spy(new FileInputStream(new File("tmp/test.txt"))); Response testObj= spy(new Response( HttpStatus.SC_OK, Collections.emptyMap(), inputStream)); testObj.getContent(); // way too many examples verify(inputStream, times(2)).close(); verify(inputStream, atMost(2)).close(); verify(inputStream, timeout(100).atLeastOnce()).close(); verify(inputStream, never()).reset(); verify(testObj).getResponseHeader(anyString()); verify(testObj).getResponseHeader(eq(HttpUtil.CONTENT_TYPE)); }
  27. 27. Matchers make it fit ๏ org.mockito.Matchers, e.g.: • any(Class<T>), anyBoolean() • isA(Class<T>), eq(T), refEq(T, String…) • isNull(Class<T>), notNull() • same(T), contains(String) • matches(String) ๏ Use org.hamcrest.Matchers with, e.g.: • argThat(Matcher<T>) • charThat(Matcher<Char>), • booleanThat(Matcher<Boolean>)
  28. 28. Let’s code!
  29. 29. Add unit tests createItem(Item) : Item getItem(String) : Item updateItem(Item) : Item ItemService + init() : DB + get(String) : Item + store(Item) : DB + delete(String) : DB + exists(Item) : boolean - singleton : DB DB + load(String) : Properties + save(String, Properties) : IPropertiesStore + remove(String) : IPropertiesStore IPropertiesStore + load(String) : Properties + save(String, Properties) : IPropertiesStore + remove(String) : IPropertiesStore - data : Map<String, Properties> InMemoryPropertiesStore datastorage + getPath() : String + getProperties() : Properties + setProperties(Properties) : Item - path : String Item + hasProperty(Object) : boolean +setValue(Object, Object) : Properties + getValue(Object key) : Object - data : Map<Object, Object> Properties properties
  30. 30. Get the Code. https://github.com/jwloka/mocktesting/ git clone ssh://git@github.com/jwloka/mocktesting.git Run it. $ mvn clean install
  31. 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!
  32. 32. Get the solution. git checkout tags/v2
  33. 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. 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. 35. Ready. Set. Go. • Unit Testing In The Real World • More guidance. More examples. • How to write unit tests? • Measure test coverage.
  36. 36. Thank You. jan.wloka@quatico.com @crashtester

×