Home | History | Annotate | Download | only in mockito
      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