001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014package org.atteo.moonshine.tests; 015 016import java.io.IOException; 017import java.lang.reflect.Field; 018import java.util.Collections; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022 023import org.atteo.moonshine.Moonshine; 024import org.atteo.moonshine.MoonshineException; 025import org.junit.rules.MethodRule; 026import org.junit.rules.TestRule; 027import org.junit.runner.Description; 028import org.junit.runner.RunWith; 029import org.junit.runners.model.FrameworkMethod; 030import org.junit.runners.model.Statement; 031import static org.mockito.Mockito.mock; 032 033import com.google.common.collect.Lists; 034import com.google.inject.Binder; 035import com.google.inject.Injector; 036import com.google.inject.Module; 037import com.google.inject.TypeLiteral; 038import com.google.inject.servlet.GuiceFilter; 039 040/** 041 * JUnit {@link TestRule rule} which initializes {@link Moonshine} container. 042 * 043 * <p> 044 * It is better for your test class to extend {@link MoonshineTest} or have it 045 * annotated with 046 * @{@link RunWith}({@link MoonshineRunner MoonshineRunner.class}). With 047 * those solutions the test class is created using Guice injector. 048 * </p> 049 * 050 * <p> 051 * Usage: 052 * <pre> 053 * class Test { 054 * @ClassRule 055 * public static final MoonshineRule moonshine = new MoonshineRule(); 056 * @Rule 057 * public MethodRule injections = moonshine.injectMembers(this); 058 * } 059 * </pre> 060 * </p> 061 */ 062public class MoonshineRule implements TestRule { 063 public final static String TEST_CONFIG = "/test-config.xml"; 064 private final String[] configs; 065 private Moonshine moonshine; 066 private final Map<Class<?>, Object> mocks = new HashMap<>(); 067 private List<MoonshineConfigurator> configurators = Collections.emptyList(); 068 private boolean loadTestConfigXml; 069 070 Map<Class<?>, Object> getMocks() { 071 return mocks; 072 } 073 074 /** 075 * Initializes {@link Moonshine} environment. 076 * 077 * <p> 078 * Usage: 079 * <pre> 080 * class Test { 081 * @ClassRule 082 * public static final MoonshineRule moonshine = new MoonshineRule(); 083 * } 084 * </pre> 085 * </p> 086 * 087 * @param configs resource path to the configuration files, by default 088 * "/test-config.xml" 089 */ 090 public MoonshineRule(String... configs) { 091 this.configs = configs; 092 loadTestConfigXml = configs.length == 0; 093 } 094 095 /** 096 * Initializes {@link Moonshine} environment. 097 * 098 * @param configurator {@link MoonshineConfigurator configurator} for Moonshine 099 * @param configs resource path to the configuration files 100 */ 101 public MoonshineRule(MoonshineConfigurator configurator, String... configs) { 102 this.configurators = Lists.newArrayList(configurator); 103 this.configs = configs; 104 loadTestConfigXml = configs.length == 0; 105 } 106 107 /** 108 * Initializes {@link Moonshine} environment. 109 * 110 * @param configurators list of {@link MoonshineConfigurator configurators} for Moonshine 111 * @param configs resource path to the configuration files 112 */ 113 public MoonshineRule(List<MoonshineConfigurator> configurators, String... configs) { 114 this.configurators = configurators; 115 this.configs = configs; 116 loadTestConfigXml = configs.length == 0; 117 } 118 119 public void setLoadTestConfigXml(boolean loadTestConfigXml) { 120 this.loadTestConfigXml = loadTestConfigXml; 121 } 122 123 @Override 124 public Statement apply(final Statement base, final Description method) { 125 return new Statement() { 126 @Override 127 public void evaluate() throws Throwable { 128 try (Moonshine moonshine = buildMoonshine(method.getTestClass())) { 129 MoonshineRule.this.moonshine = moonshine; 130 if (moonshine != null) { 131 moonshine.start(); 132 } 133 134 base.evaluate(); 135 } 136 // Workaround for the WARNING: Multiple Servlet injectors detected. 137 new GuiceFilter().destroy(); 138 MoonshineRule.this.moonshine = null; 139 } 140 }; 141 } 142 143 private Moonshine buildMoonshine(final Class<?> testClass) throws MoonshineException { 144 try { 145 Moonshine.Builder builder = Moonshine.Factory.builder(); 146 147 Module testClassModule = new Module() { 148 @Override 149 public void configure(Binder binder) { 150 binder.bind(testClass); 151 } 152 }; 153 154 final Field fields[] = testClass.getDeclaredFields(); 155 156 for (final Field field : fields) { 157 if (field.isAnnotationPresent(MockAndBind.class)) { 158 Object object = mock(field.getType()); 159 mocks.put(field.getType(), object); 160 } 161 } 162 163 Module mocksModule = new Module() { 164 @SuppressWarnings("unchecked") 165 @Override 166 public void configure(final Binder binder) { 167 // TODO: add support for binding annotated objects 168 for (Class<?> klass : mocks.keySet()) { 169 @SuppressWarnings("rawtypes") 170 final TypeLiteral t = TypeLiteral.get(klass); 171 final Object object = mocks.get(klass); 172 binder.bind(t).toInstance(object); 173 } 174 175 binder.requestStaticInjection(testClass); 176 } 177 }; 178 179 builder.applicationName(testClass.getSimpleName()); 180 builder.homeDirectory("target/test-home"); 181 builder.addDataDir("src/main"); 182 183 for (String config : configs) { 184 builder.addConfigurationFromResource(config); 185 } 186 187 if (loadTestConfigXml) { 188 builder.addOptionalConfigurationFromResource(TEST_CONFIG); 189 } 190 191 builder.addModule(testClassModule); 192 builder.addModule(mocksModule); 193 194 for (MoonshineConfigurator configurator : configurators) { 195 configurator.configureMoonshine(builder); 196 } 197 return builder.build(); 198 } catch (IOException e) { 199 throw new RuntimeException(e); 200 } 201 202 } 203 204 /** 205 * Returns global {@link Injector}. 206 */ 207 public Injector getGlobalInjector() { 208 return moonshine.getGlobalInjector(); 209 } 210 211 /** 212 * Returns the rule which injects members of given object on each test run. 213 * <p> 214 * Usage: 215 * <pre> 216 * class Test { 217 * @ClassRule 218 * public static final MoonshineRule moonshine = new MoonshineRule(); 219 * @Rule 220 * public MethodRule injections = moonshine.injectMembers(this); 221 * } 222 * </pre> 223 * </p> 224 * 225 * @param object object to inject members into 226 * @return the method rule to use with JUnit 227 */ 228 public MethodRule injectMembers(Object object) { 229 return new MethodRule() { 230 @Override 231 public Statement apply(final Statement base, FrameworkMethod method, final Object target) { 232 return new Statement() { 233 @Override 234 public void evaluate() throws Throwable { 235 if (getGlobalInjector() != null) { 236 getGlobalInjector().injectMembers(target); 237 } 238 base.evaluate(); 239 } 240 }; 241 } 242 }; 243 } 244}