/* * $Id$ * * License Agreement. * * Rich Faces - Natural Ajax for Java Server Faces (JSF) * * Copyright (C) 2007 Exadel, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package org.richfaces.cdk; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.easymock.EasyMock; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import com.google.inject.AbstractModule; import com.google.inject.BindingAnnotation; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.Provider; import com.google.inject.ScopeAnnotation; import com.google.inject.TypeLiteral; import com.google.inject.binder.AnnotatedBindingBuilder; /** * <p class="changed_added_4_0"> * </p> * * @author asmirnov@exadel.com * */ public class CdkTestRunner extends BlockJUnit4ClassRunner { /** * <p class="changed_added_4_0"> * </p> * * @param klass * @throws InitializationError * @throws InitializationError */ public CdkTestRunner(Class<?> klass) throws InitializationError { super(klass); } /** * Gets all declared fields and all inherited fields. */ protected Set<Field> getFields(Class<?> c) { Set<Field> fields = new HashSet<Field>(Arrays.asList(c.getDeclaredFields())); while ((c = c.getSuperclass()) != null) { for (Field f : c.getDeclaredFields()) { if (!Modifier.isStatic(f.getModifiers()) && !Modifier.isPrivate(f.getModifiers())) { fields.add(f); } } } return fields; } @Override protected void runChild(FrameworkMethod method, RunNotifier notifier) { super.runChild(method, notifier); } @Override protected Object createTest() throws Exception { Class<?> c = getTestClass().getJavaClass(); Set<Field> testFields = getFields(c); // make sure we have one (and only one) @Unit field // Field unitField = getUnitField(testFields); // if ( unitField.getAnnotation(Mock.class) != null ) { // throw new IncompatibleAnnotationException(Unit.class, Mock.class); // } // final Map<Field, Binding> fieldValues = getMockValues(testFields); // if ( fieldValues.containsKey(unitField)) { // throw new IncompatibleAnnotationException(Unit.class, unitField.getType()); // } Object test = createTest(c, fieldValues); // any field values created by AtUnit but not injected by the container are injected here. for (Field field : fieldValues.keySet()) { Binding binding = fieldValues.get(field); field.setAccessible(true); if (null != binding.getValue() && field.get(test) == null) { field.set(test, binding.getValue()); } } return test; } private Object createTest(Class<?> testClass, Map<Field, Binding> fieldValues) throws Exception { FieldModule fields = new FieldModule(fieldValues); Injector injector; Object test = super.createTest(); if (Module.class.isAssignableFrom(testClass)) { injector = Guice.createInjector(fields, (Module) testClass.newInstance()); } else { injector = Guice.createInjector(fields); } injector.injectMembers(test); return test; } protected static final class FieldModule extends AbstractModule implements MockController { final Map<Field, Binding> fields; public FieldModule(Map<Field, Binding> fields) { this.fields = fields; } @Override @SuppressWarnings("unchecked") protected void configure() { // Bind mock controllet to this instance, to automatically replay/verify all mocks created by runner. bind(MockController.class).toInstance(this); // map field values by type for (Field field : fields.keySet()) { TypeLiteral literal = TypeLiteral.get(field.getGenericType()); AnnotatedBindingBuilder builder = bind(literal); // Check field annotations. Annotation[] fieldAnnotations = field.getAnnotations(); for (Annotation annotation : fieldAnnotations) { Class<? extends Annotation> annotationType = annotation.annotationType(); if (/* annotationType.isAnnotationPresent(Qualifier.class)|| */annotationType .isAnnotationPresent(BindingAnnotation.class)) { builder.annotatedWith(annotation); } if (annotationType.isAnnotationPresent(ScopeAnnotation.class)) { builder.in(annotationType); } } Binding binding = fields.get(field); if (null != binding.getValue()) { builder.toInstance(binding.getValue()); } else if (null != binding.getImplementation()) { builder.to(binding.getImplementation()); } else if (null != binding.getProvider()) { builder.toProvider(binding.getProvider()); } } } @Override public void replay() { for (Binding field : fields.values()) { if (null != field.getValue()) { EasyMock.replay(field.getValue()); } } } @Override public void verify() { for (Binding field : fields.values()) { if (null != field.getValue()) { EasyMock.verify(field.getValue()); } } } } /** * <p class="changed_added_4_0"> * Binding definition storage * </p> * * @author asmirnov@exadel.com * */ protected static final class Binding { private Object value; private Class<?> implementation; private Class<? extends Provider<?>> provider; protected Binding() { } /** * <p class="changed_added_4_0"> * </p> * * @param value the value to set */ void setValue(Object value) { this.value = value; } /** * <p class="changed_added_4_0"> * </p> * * @return the value */ Object getValue() { return value; } /** * <p class="changed_added_4_0"> * </p> * * @param implementation the implementation to set */ void setImplementation(Class<?> implementation) { this.implementation = implementation; } /** * <p class="changed_added_4_0"> * </p> * * @return the implementation */ Class<?> getImplementation() { return implementation; } /** * <p class="changed_added_4_0"> * </p> * * @param provider the provider to set */ void setProvider(Class<? extends Provider<?>> provider) { this.provider = provider; } /** * <p class="changed_added_4_0"> * </p> * * @return the provider */ Class<? extends Provider<?>> getProvider() { return provider; } } private Map<Field, Binding> getMockValues(Set<Field> testFields) { Map<Field, Binding> mocksAndStubs = new HashMap<Field, Binding>(); // TODO - create annotation attribute that tells runner to use the scme Mock Controller to create related mocks. for (Field field : testFields) { if (field.getAnnotation(Mock.class) != null) { Binding bind = new Binding(); bind.setValue(EasyMock.createStrictMock(field.getType())); mocksAndStubs.put(field, bind); } else if (field.getAnnotation(Stub.class) != null) { Binding bind = new Binding(); bind.setValue(EasyMock.createNiceMock(field.getType())); mocksAndStubs.put(field, bind); } else if (null != field.getAnnotation(As.class)) { Binding bind = new Binding(); bind.setImplementation(field.getAnnotation(As.class).value()); mocksAndStubs.put(field, bind); } else if (null != field.getAnnotation(AsProvider.class)) { Binding bind = new Binding(); bind.setProvider(field.getAnnotation(AsProvider.class).value()); mocksAndStubs.put(field, bind); } } return mocksAndStubs; } }