001package com.avaje.ebean;
002
003
004import java.util.concurrent.Callable;
005
006/**
007 * This is a test helper class that can be used to swap out the default EbeanServer in the Ebean
008 * singleton with a mock implementation.
009 * <p>
010 * This enables a developer to write a test using a tool like Mockito to mock the EbeanServer
011 * interface and make this the default server of the Ebean singleton.
012 * <p>
013 * <p>
014 *
015 * </p>
016 * <pre>{@code
017 *
018 *  EbeanServer mock = ...; // create a mock or test double etc
019 *
020 *  MockiEbean.runWithMock(mock, new Runnable() {
021 *
022 *    public void run() {
023 *      ...
024 *      // test code in here runs with mock EbeanServer
025 *    }
026 *  });
027 *
028 * }</pre>
029 * <p>
030 * An example using Mockito to mock the getBeanId() method on EbeanServer.
031 * <p>
032 * <pre>{@code
033 *
034 * @Test
035 * public void testWithMockito() {
036 *
037 *   EbeanServer defaultServer = Ebean.getServer(null);
038 *   assertTrue("is a real EbeanServer", defaultServer instanceof DefaultServer);
039 *
040 *   Long magicBeanId = Long.valueOf(47L);
041 *
042 *   EbeanServer mock = Mockito.mock(EbeanServer.class);
043 *   when(mock.getBeanId(null)).thenReturn(magicBeanId);
044 *
045 *   MockiEbean mockiEbean = MockiEbean.start(mock);
046 *   try {
047 *
048 *     // So using the Ebean singleton returns the mock instance
049 *     EbeanServer server = Ebean.getServer(null);
050 *     Object beanId = server.getBeanId(null);
051 *
052 *     assertEquals(magicBeanId, beanId);
053 *
054 *   } finally {
055 *     mockiEbean.restoreOriginal();
056 *   }
057 *
058 *   EbeanServer restoredServer = Ebean.getServer(null);
059 *   assertTrue("is a real EbeanServer", restoredServer instanceof DefaultServer);
060 * }
061 *
062 * }</pre>
063 */
064public class MockiEbean {
065
066  /**
067   * Set a mock implementation of EbeanServer as the default server.
068   * <p>
069   * Typically the mock instance passed in is created by Mockito or similar tool.
070   * <p>
071   * The default EbeanSever is the instance returned by {@link Ebean#getServer(String)} when the
072   * server name is null.
073   *
074   * @param mock the mock instance that becomes the default EbeanServer
075   * @return The MockiEbean with a {@link #restoreOriginal()} method that can be used to restore the
076   * original EbeanServer implementation.
077   */
078  public static MockiEbean start(EbeanServer mock) {
079
080    // using $mock as the server name
081    EbeanServer original = Ebean.mock("$mock", mock, true);
082
083    if (mock instanceof DelegateAwareEbeanServer) {
084      ((DelegateAwareEbeanServer)mock).withDelegateIfRequired(original);
085    }
086
087    return new MockiEbean(mock, original);
088  }
089
090  /**
091   * Run the test runnable using the mock EbeanServer and restoring the original EbeanServer afterward.
092   *
093   * @param mock the mock instance that becomes the default EbeanServer
094   * @param test typically some test code as a runnable
095   */
096  public static void runWithMock(EbeanServer mock, Runnable test) {
097
098    start(mock).run(test);
099  }
100
101  /**
102   * Run the test runnable using the mock EbeanServer and restoring the original EbeanServer afterward.
103   *
104   * @param mock the mock instance that becomes the default EbeanServer
105   * @param test typically some test code as a callable
106   */
107  public static <V> V runWithMock(EbeanServer mock, Callable<V> test) throws Exception {
108
109    return start(mock).run(test);
110  }
111
112  /**
113   * The 'original' default EbeanServer that the mock is temporarily replacing.
114   */
115  protected final EbeanServer original;
116
117  /**
118   * The 'mock' EbeanServer that is temporarily replacing the 'original' during the 'run'.
119   */
120  protected final EbeanServer mock;
121
122  /**
123   * Construct with the mock and original EbeanServer instances.s
124   */
125  protected MockiEbean(EbeanServer mock, EbeanServer original) {
126    this.mock = mock;
127    this.original = original;
128  }
129
130  /**
131   * Return the original EbeanServer implementation.
132   * <p>
133   * This is the implementation that is put back as the default EbeanServer when
134   * {@link #restoreOriginal()} is called.
135   */
136  public EbeanServer getOriginal() {
137    return original;
138  }
139
140  /**
141   * Return the mock EbeanServer instance that was set as the default EbeanServer.
142   */
143  public EbeanServer getMock() {
144    return mock;
145  }
146
147  /**
148   * Run the test runnable restoring the original EbeanServer afterwards.
149   */
150  public void run(Runnable testRunnable) {
151    try {
152      beforeRun();
153      testRunnable.run();
154    } finally {
155      afterRun();
156      restoreOriginal();
157    }
158  }
159
160
161  /**
162   * Run the test callable restoring the original EbeanServer afterwards.
163   */
164  public <V> V run(Callable<V> testCallable) throws Exception {
165    try {
166      beforeRun();
167      return testCallable.call();
168    } finally {
169      afterRun();
170      restoreOriginal();
171    }
172  }
173
174
175  /**
176   * Typically only used internally.
177   *
178   * Usually used to set static Finder test doubles.
179   */
180  public void beforeRun() {
181    if (mock instanceof DelegateAwareEbeanServer) {
182      ((DelegateAwareEbeanServer)mock).beforeRun();
183    }
184  }
185
186  /**
187   * Typically only used internally.
188   *
189   * Usually used to restore static Finder original implementations.
190   */
191  public void afterRun() {
192    if (mock instanceof DelegateAwareEbeanServer) {
193      ((DelegateAwareEbeanServer)mock).afterRun();
194    }
195  }
196
197  /**
198   * Restore the original EbeanServer implementation as the default EbeanServer.
199   */
200  public void restoreOriginal() {
201    if (original == null) {
202      throw new IllegalStateException("Original EbeanServer instance is null");
203    }
204    if (original.getName() == null) {
205      throw new IllegalStateException("Original EbeanServer name is null");
206    }
207
208    // restore the original EbeanServer back
209    Ebean.mock(original.getName(), original, true);
210  }
211
212}