1 /* 2 * Copyright (c) 2017 Mockito contributors 3 * This program is made available under the terms of the MIT License. 4 */ 5 package org.mockito; 6 7 import org.junit.Before; 8 import org.junit.Test; 9 import org.mockito.exceptions.verification.NoInteractionsWanted; 10 import org.mockito.exceptions.verification.WantedButNotInvoked; 11 import org.mockito.exceptions.verification.junit.ArgumentsAreDifferent; 12 import org.mockito.invocation.Invocation; 13 import org.mockito.invocation.InvocationFactory; 14 import org.mockito.invocation.MockHandler; 15 import org.mockitoutil.TestBase; 16 17 import java.lang.reflect.Constructor; 18 import java.lang.reflect.Method; 19 20 import static org.assertj.core.api.Assertions.assertThat; 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.fail; 23 import static org.mockito.Mockito.doReturn; 24 import static org.mockito.Mockito.times; 25 import static org.mockito.Mockito.verify; 26 import static org.mockito.Mockito.verifyNoMoreInteractions; 27 import static org.mockito.Mockito.when; 28 import static org.mockito.Mockito.withSettings; 29 30 /** 31 * This test is an experimental use of Mockito API to simulate static mocking. 32 * Other frameworks can use it to build good support for static mocking. 33 * Keep in mind that clean code never needs to mock static methods. 34 * This test is a documentation how it can be done using current public API of Mockito. 35 * This test is not only an experiment it also provides coverage for 36 * some of the advanced public API exposed for framework integrators. 37 * <p> 38 * For more rationale behind this experimental test 39 * <a href="https://www.linkedin.com/pulse/mockito-vs-powermock-opinionated-dogmatic-static-mocking-faber">see the article</a>. 40 */ 41 public class StaticMockingExperimentTest extends TestBase { 42 43 Foo mock = Mockito.mock(Foo.class); 44 MockHandler handler = Mockito.mockingDetails(mock).getMockHandler(); 45 Method staticMethod; 46 InvocationFactory.RealMethodBehavior realMethod = new InvocationFactory.RealMethodBehavior() { 47 @Override 48 public Object call() throws Throwable { 49 return null; 50 } 51 }; 52 53 @Before public void before() throws Throwable { 54 staticMethod = Foo.class.getDeclaredMethod("staticMethod", String.class); 55 } 56 57 @SuppressWarnings({"CheckReturnValue", "MockitoUsage"}) 58 @Test 59 public void verify_static_method() throws Throwable { 60 //register staticMethod call on mock 61 Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod, 62 "some arg"); 63 handler.handle(invocation); 64 65 //verify staticMethod on mock 66 //Mockito cannot capture static methods so we will simulate this scenario in 3 steps: 67 //1. Call standard 'verify' method. Internally, it will add verificationMode to the thread local state. 68 // Effectively, we indicate to Mockito that right now we are about to verify a method call on this mock. 69 verify(mock); 70 //2. Create the invocation instance using the new public API 71 // Mockito cannot capture static methods but we can create an invocation instance of that static invocation 72 Invocation verification = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod, 73 "some arg"); 74 //3. Make Mockito handle the static method invocation 75 // Mockito will find verification mode in thread local state and will try verify the invocation 76 handler.handle(verification); 77 78 //verify zero times, method with different argument 79 verify(mock, times(0)); 80 Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod, 81 "different arg"); 82 handler.handle(differentArg); 83 } 84 85 @SuppressWarnings({"CheckReturnValue", "MockitoUsage"}) 86 @Test 87 public void verification_failure_static_method() throws Throwable { 88 //register staticMethod call on mock 89 Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod, 90 "foo"); 91 handler.handle(invocation); 92 93 //verify staticMethod on mock 94 verify(mock); 95 Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod, 96 "different arg"); 97 98 try { 99 handler.handle(differentArg); 100 fail(); 101 } catch (ArgumentsAreDifferent e) {} 102 } 103 104 @Test 105 public void stubbing_static_method() throws Throwable { 106 //register staticMethod call on mock 107 Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod, 108 "foo"); 109 handler.handle(invocation); 110 111 //register stubbing 112 when(null).thenReturn("hey"); 113 114 //validate stubbed return value 115 assertEquals("hey", handler.handle(invocation)); 116 assertEquals("hey", handler.handle(invocation)); 117 118 //default null value is returned if invoked with different argument 119 Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod, 120 "different arg"); 121 assertEquals(null, handler.handle(differentArg)); 122 } 123 124 @Test 125 public void do_answer_stubbing_static_method() throws Throwable { 126 //register stubbed return value 127 doReturn("hey").when(mock); 128 129 //complete stubbing by triggering an invocation that needs to be stubbed 130 Invocation invocation = Mockito.framework().getInvocationFactory() 131 .createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod, "foo"); 132 handler.handle(invocation); 133 134 //validate stubbed return value 135 assertEquals("hey", handler.handle(invocation)); 136 assertEquals("hey", handler.handle(invocation)); 137 138 //default null value is returned if invoked with different argument 139 Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod, 140 "different arg"); 141 assertEquals(null, handler.handle(differentArg)); 142 } 143 144 @Test 145 public void verify_no_more_interactions() throws Throwable { 146 //works for now because there are not interactions 147 verifyNoMoreInteractions(mock); 148 149 //register staticMethod call on mock 150 Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod, 151 "foo"); 152 handler.handle(invocation); 153 154 //fails now because we have one static invocation recorded 155 try { 156 verifyNoMoreInteractions(mock); 157 fail(); 158 } catch (NoInteractionsWanted e) {} 159 } 160 161 @Test 162 public void stubbing_new() throws Throwable { 163 Constructor<Foo> ctr = Foo.class.getConstructor(String.class); 164 Method adapter = ConstructorMethodAdapter.class.getDeclaredMethods()[0]; 165 166 //stub constructor 167 doReturn(new Foo("hey!")).when(mock); 168 Invocation constructor = Mockito.framework().getInvocationFactory().createInvocation( 169 mock, withSettings().build(Foo.class), adapter, realMethod, ctr, "foo"); 170 handler.handle(constructor); 171 172 //test stubbing 173 Object result = handler.handle(constructor); 174 assertEquals("foo:hey!", result.toString()); 175 176 //stubbing miss 177 Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), 178 adapter, realMethod, ctr, "different arg"); 179 Object result2 = handler.handle(differentArg); 180 assertEquals(null, result2); 181 } 182 183 @SuppressWarnings({"CheckReturnValue", "MockitoUsage"}) 184 @Test 185 public void verifying_new() throws Throwable { 186 Constructor<Foo> ctr = Foo.class.getConstructor(String.class); 187 Method adapter = ConstructorMethodAdapter.class.getDeclaredMethods()[0]; 188 189 //invoke constructor 190 Invocation constructor = Mockito.framework().getInvocationFactory().createInvocation( 191 mock, withSettings().build(Foo.class), adapter, realMethod, ctr, "matching arg"); 192 handler.handle(constructor); 193 194 //verify successfully 195 verify(mock); 196 handler.handle(constructor); 197 198 //verification failure 199 verify(mock); 200 Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), 201 adapter, realMethod, ctr, "different arg"); 202 try { 203 handler.handle(differentArg); 204 fail(); 205 } catch (WantedButNotInvoked e) { 206 assertThat(e.getMessage()) 207 .contains("matching arg") 208 .contains("different arg"); 209 } 210 } 211 212 static class Foo { 213 214 private final String arg; 215 216 public Foo(String arg) { 217 this.arg = arg; 218 } 219 220 public static String staticMethod(String arg) { 221 return ""; 222 } 223 224 @Override 225 public String toString() { 226 return "foo:" + arg; 227 } 228 } 229 230 /** 231 * Adapts constructor to method calls needed to work with Mockito API. 232 */ 233 interface ConstructorMethodAdapter { 234 Object construct(Constructor constructor, Object... args); 235 } 236 } 237