4 Replies Latest reply on Jun 22, 2007 5:20 PM by stu2

    UnitTesting Components

    knisterpeter

      Hi,

      I've implemented my ejbs and pojos using the Controller base classes of the seam framework package.
      This compontens are not unit testable anymore, because when adding a message to FacesMessages an exception is thrown that no Conversion is available.

      How to specify a mock for a temporary context?

        • 1. Re: UnitTesting Components
          stu2

          Could you post example code? If you're extending SeamTest the mock contexts are setup for you.

          • 2. Re: UnitTesting Components
            knisterpeter

            Ok, I've had no idea that this was the case. I have to port SeamTest before testing again, since I'm using JUnit4 instead of TestNG.

            • 3. Re: UnitTesting Components
              knisterpeter

              Ok, below is the JUnit4 version of SeamTest. If there is interest it could be included into seam. There are only small changes which could be refactored into a subclass so both TestNG and JUnit4 share a common base class.
              In details the differences are the init and shutdown methods annotated with @Configuration.

              But I still have a problem: I need a mock instance for one of my beans and therefore (as stated in the docs) I extend my bean and set the @Install with a precedence of MOCK, but the mock bean is not included and started in the container. Are there examples on how to use the MOCK precedence? I haven't found any in the source and examples.

              /**
               * $Id$
               */
              package de.llynch.kingpin.bean;
              
              import java.lang.reflect.Field;
              import java.util.AbstractSet;
              import java.util.Collections;
              import java.util.HashMap;
              import java.util.Iterator;
              import java.util.List;
              import java.util.Map;
              import java.util.Set;
              
              import javax.faces.application.Application;
              import javax.faces.application.FacesMessage;
              import javax.faces.component.UIViewRoot;
              import javax.faces.context.FacesContext;
              import javax.faces.event.PhaseEvent;
              import javax.faces.event.PhaseId;
              import javax.naming.InitialContext;
              import javax.naming.NamingException;
              import javax.servlet.http.Cookie;
              import javax.servlet.http.HttpServletRequest;
              import javax.servlet.http.HttpSession;
              import javax.transaction.UserTransaction;
              
              import org.hibernate.validator.ClassValidator;
              import org.hibernate.validator.InvalidValue;
              import org.jboss.seam.Component;
              import org.jboss.seam.Model;
              import org.jboss.seam.actionparam.ActionParamMethodBinding;
              import org.jboss.seam.contexts.Contexts;
              import org.jboss.seam.contexts.Lifecycle;
              import org.jboss.seam.core.Expressions;
              import org.jboss.seam.core.FacesMessages;
              import org.jboss.seam.core.Init;
              import org.jboss.seam.core.Manager;
              import org.jboss.seam.core.Pageflow;
              import org.jboss.seam.init.Initialization;
              import org.jboss.seam.jsf.AbstractSeamPhaseListener;
              import org.jboss.seam.jsf.SeamApplication11;
              import org.jboss.seam.jsf.SeamNavigationHandler;
              import org.jboss.seam.jsf.SeamPhaseListener;
              import org.jboss.seam.jsf.SeamStateManager;
              import org.jboss.seam.mock.MockApplication;
              import org.jboss.seam.mock.MockExternalContext;
              import org.jboss.seam.mock.MockFacesContext;
              import org.jboss.seam.mock.MockHttpServletRequest;
              import org.jboss.seam.mock.MockHttpSession;
              import org.jboss.seam.mock.MockLifecycle;
              import org.jboss.seam.mock.MockServletContext;
              import org.jboss.seam.servlet.ServletSessionImpl;
              import org.jboss.seam.util.Naming;
              import org.jboss.seam.util.Reflections;
              import org.jboss.seam.util.Transactions;
              import org.junit.After;
              import org.junit.AfterClass;
              import org.junit.Before;
              import org.junit.BeforeClass;
              
              /**
               * Code is copied from SeamTest which is for TestNG instead of JUnit4.
               *
               * @author Markus Wolf
               */
              public abstract class AbstractSeamTest {
              
               private static MockExternalContext externalContext;
              
               private static MockServletContext servletContext;
              
               private static MockApplication application;
              
               private static AbstractSeamPhaseListener phases;
              
               private MockHttpSession session;
              
               private static Map<String, Map> conversationViewRootAttributes;
              
               private final Map<String, Object> pageParameters = new HashMap<String, Object>();
              
               protected void setParameter(final String name, final String value) {
               this.getParameters().put(name, new String[] { value });
               }
              
               protected void setPageParameter(final String name, final Object value) {
               this.pageParameters.put(name, value);
               }
              
               protected Map<String, String[]> getParameters() {
               return ((MockHttpServletRequest) this.externalContext.getRequest())
               .getParameters();
               }
              
               protected Map<String, String[]> getHeaders() {
               return ((MockHttpServletRequest) this.externalContext.getRequest())
               .getHeaders();
               }
              
               protected HttpSession getSession() {
               return (HttpSession) this.externalContext.getSession(true);
               }
              
               protected boolean isSessionInvalid() {
               return ((MockHttpSession) this.getSession()).isInvalid();
               }
              
               /**
               * Helper method for resolving components in the test script.
               */
               protected Object getInstance(final Class clazz) {
               return Component.getInstance(clazz);
               }
              
               /**
               * Helper method for resolving components in the test script.
               */
               protected Object getInstance(final String name) {
               return Component.getInstance(name);
               }
              
               /**
               * Is there a long running conversation associated with the current request?
               */
               protected boolean isLongRunningConversation() {
               return Manager.instance().isLongRunningConversation();
               }
              
               /**
               * Search in all contexts
               */
               public Object lookup(final String name) {
               return Contexts.lookupInStatefulContexts(name);
               }
              
               /**
               * @deprecated use FacesRequest or NonFacesRequest
               * @author Gavin King
               */
               @Deprecated
               public abstract class Script extends Request {
               public Script() {
               }
              
               public Script(final String conversationId) {
               super(conversationId);
               }
               }
              
               /**
               * Script is an abstract superclass for usually anonymous inner classes that
               * test JSF interactions.
               *
               * @author Gavin King
               */
               abstract class Request {
               private String conversationId;
              
               private String outcome;
              
               private boolean validationFailed;
              
               private MockFacesContext facesContext;
              
               private String viewId;
              
               private boolean renderResponseBegun;
              
               private boolean renderResponseComplete;
              
               private boolean invokeApplicationBegun;
              
               private boolean invokeApplicationComplete;
              
               private Application application;
              
               /**
               * Override to define the name of the current principal
               *
               * @return "gavin" by default
               */
               public String getPrincipalName() {
               return "gavin";
               }
              
               /**
               * Override to define the roles assigned to the current principal
               *
               * @return a Set of all roles by default
               */
               public Set<String> getPrincipalRoles() {
               return new AbstractSet<String>() {
               @Override
               public boolean contains(final Object o) {
               return true;
               }
              
               @Override
               public Iterator<String> iterator() {
               throw new UnsupportedOperationException();
               }
              
               @Override
               public int size() {
               throw new UnsupportedOperationException();
               }
               };
               }
              
               public List<Cookie> getCookies() {
               return Collections.EMPTY_LIST;
               }
              
               /**
               * A script for a JSF interaction with no existing long-running
               * conversation.
               */
               protected Request() {
               }
              
               /**
               * A script for a JSF interaction in the scope of an existing
               * long-running conversation.
               */
               protected Request(final String conversationId) {
               this.conversationId = conversationId;
               }
              
               /**
               * Is this a non-faces request? Override if it is.
               *
               * @return false by default
               */
               protected boolean isGetRequest() {
               return false;
               }
              
               /**
               * The JSF view id of the form that is being submitted or of the page
               * that is being rendered in a non-faces request. (override if you need
               * page actions to be called, and page parameters applied)
               */
               protected String getViewId() {
               return this.viewId;
               }
              
               protected void setViewId(final String viewId) {
               this.viewId = viewId;
               }
              
               /**
               * Override to implement the interactions between the JSF page and your
               * components that occurs during the apply request values phase.
               */
               protected void applyRequestValues() throws Exception {
               }
              
               /**
               * Override to implement the interactions between the JSF page and your
               * components that occurs during the process validations phase.
               */
               protected void processValidations() throws Exception {
               }
              
               /**
               * Override to implement the interactions between the JSF page and your
               * components that occurs during the update model values phase.
               */
               protected void updateModelValues() throws Exception {
               }
              
               /**
               * Override to implement the interactions between the JSF page and your
               * components that occurs during the invoke application phase.
               */
               protected void invokeApplication() throws Exception {
               }
              
               /**
               * Set the outcome of the INVOKE_APPLICATION phase
               */
               protected void setOutcome(final String outcome) {
               this.outcome = outcome;
               }
              
               /**
               * Get the outcome of the INVOKE_APPLICATION phase
               */
               protected String getInvokeApplicationOutcome() {
               return this.outcome;
               }
              
               /**
               * Override to implement the interactions between the JSF page and your
               * components that occurs during the render response phase.
               */
               protected void renderResponse() throws Exception {
               }
              
               /**
               * Override to set up any request parameters for the request.
               *
               * @deprecated use beforeRequest()
               */
               @Deprecated
               protected void setup() {
               }
              
               /**
               * Make some assertions, after the end of the request.
               */
               protected void afterRequest() {
               }
              
               /**
               * Do anything you like, after the start of the request. Especially, set
               * up any request parameters for the request.
               */
               protected void beforeRequest() {
               this.setup();
               }
              
               /**
               * Get the view id to be rendered
               *
               * @return the JSF view id
               */
               protected String getRenderedViewId() {
               if (Init.instance().isJbpmInstalled()
               && Pageflow.instance().isInProcess()) {
               return Pageflow.instance().getPageViewId();
               } else {
               // TODO: not working right now, 'cos no mock navigation handler!
               return this.getFacesContext().getViewRoot().getViewId();
               }
               }
              
               /**
               * @deprecated use validateValue()
               */
               @Deprecated
               protected void validate(final Class modelClass, final String property,
               final Object value) {
               final ClassValidator validator = Model.forClass(modelClass)
               .getValidator();
               final InvalidValue[] ivs = validator.getPotentialInvalidValues(
               property, value);
               if (ivs.length > 0) {
               this.validationFailed = true;
               final FacesMessage message = FacesMessages.createFacesMessage(
               FacesMessage.SEVERITY_WARN, ivs[0].getMessage());
               FacesContext.getCurrentInstance().addMessage(property, /* TODO */
               message);
               FacesContext.getCurrentInstance().renderResponse();
               }
               }
              
               /**
               * Did a validation failure occur during a call to validate()?
               */
               protected boolean isValidationFailure() {
               return this.validationFailed;
               }
              
               protected FacesContext getFacesContext() {
               return this.facesContext;
               }
              
               protected String getConversationId() {
               return this.conversationId;
               }
              
               /**
               * Evaluate (get) a value binding
               */
               protected Object getValue(final String valueExpression) {
               return this.application.createValueBinding(valueExpression)
               .getValue(this.facesContext);
               }
              
               /**
               * Set a value binding
               */
               protected void setValue(final String valueExpression, final Object value) {
               this.application.createValueBinding(valueExpression).setValue(
               this.facesContext, value);
               }
              
               /**
               * Validate the value against model-based constraints return true if the
               * value is valid
               */
               protected boolean validateValue(final String valueExpression,
               final Object value) {
               final InvalidValue[] ivs = Expressions.instance().validate(
               valueExpression, value);
               if (ivs.length > 0) {
               this.validationFailed = true;
               this.facesContext.addMessage(null, FacesMessages
               .createFacesMessage(FacesMessage.SEVERITY_WARN, ivs[0]
               .getMessage()));
               return false;
               } else {
               return true;
               }
               }
              
               /**
               * Call a method binding
               */
               protected Object invokeMethod(final String methodExpression) {
               return new ActionParamMethodBinding(this.application,
               methodExpression).invoke(this.facesContext, null);
               }
              
               /**
               * @return the conversation id
               * @throws Exception
               * to fail the test
               */
               public String run() throws Exception {
               final HttpServletRequest request = new MockHttpServletRequest(
               AbstractSeamTest.this.session, this.getPrincipalName(),
               this.getPrincipalRoles(), this.getCookies().toArray(
               new Cookie[] {}));
               AbstractSeamTest.this.externalContext = new MockExternalContext(
               AbstractSeamTest.this.servletContext, request);
               this.application = new SeamApplication11(
               AbstractSeamTest.this.application);
               this.facesContext = new MockFacesContext(
               AbstractSeamTest.this.externalContext, this.application);
               this.facesContext.setCurrent();
              
               this.beforeRequest();
              
               AbstractSeamTest.this.phases.beforePhase(new PhaseEvent(
               this.facesContext, PhaseId.RESTORE_VIEW,
               MockLifecycle.INSTANCE));
              
               final UIViewRoot viewRoot = this.facesContext.getApplication()
               .getViewHandler().createView(this.facesContext,
               this.getViewId());
               this.facesContext.setViewRoot(viewRoot);
               final Map restoredViewRootAttributes = this.facesContext
               .getViewRoot().getAttributes();
               if (this.conversationId != null) {
               if (this.isGetRequest()) {
               AbstractSeamTest.this.setParameter(Manager.instance()
               .getConversationIdParameter(), this.conversationId);
               // TODO: what about conversationIsLongRunning????
               } else {
               if (AbstractSeamTest.this.conversationViewRootAttributes
               .containsKey(this.conversationId)) {
               // should really only do this if the view id matches
               // (not really possible to implement)
               final Map state = AbstractSeamTest.this.conversationViewRootAttributes
               .get(this.conversationId);
               restoredViewRootAttributes.putAll(state);
               }
               }
               }
               if (!this.isGetRequest()) {
               restoredViewRootAttributes
               .putAll(AbstractSeamTest.this.pageParameters);
               }
              
               this.updateConversationId();
              
               AbstractSeamTest.this.phases.afterPhase(new PhaseEvent(
               this.facesContext, PhaseId.RESTORE_VIEW,
               MockLifecycle.INSTANCE));
              
               if (!this.isGetRequest() && !this.skipToRender()) {
              
               AbstractSeamTest.this.phases.beforePhase(new PhaseEvent(
               this.facesContext, PhaseId.APPLY_REQUEST_VALUES,
               MockLifecycle.INSTANCE));
              
               this.applyRequestValues();
              
               this.updateConversationId();
              
               AbstractSeamTest.this.phases.afterPhase(new PhaseEvent(
               this.facesContext, PhaseId.APPLY_REQUEST_VALUES,
               MockLifecycle.INSTANCE));
              
               if (!this.skipToRender()) {
              
               AbstractSeamTest.this.phases.beforePhase(new PhaseEvent(
               this.facesContext, PhaseId.PROCESS_VALIDATIONS,
               MockLifecycle.INSTANCE));
              
               this.processValidations();
              
               this.updateConversationId();
              
               AbstractSeamTest.this.phases.afterPhase(new PhaseEvent(
               this.facesContext, PhaseId.PROCESS_VALIDATIONS,
               MockLifecycle.INSTANCE));
              
               if (!this.skipToRender()) {
              
               AbstractSeamTest.this.phases
               .beforePhase(new PhaseEvent(this.facesContext,
               PhaseId.UPDATE_MODEL_VALUES,
               MockLifecycle.INSTANCE));
              
               this.updateModelValues();
              
               this.updateConversationId();
              
               AbstractSeamTest.this.phases.afterPhase(new PhaseEvent(
               this.facesContext, PhaseId.UPDATE_MODEL_VALUES,
               MockLifecycle.INSTANCE));
              
               if (!this.skipToRender()) {
              
               AbstractSeamTest.this.phases
               .beforePhase(new PhaseEvent(
               this.facesContext,
               PhaseId.INVOKE_APPLICATION,
               MockLifecycle.INSTANCE));
              
               this.invokeApplicationBegun = true;
              
               this.invokeApplication();
              
               this.invokeApplicationComplete = true;
              
               final String outcome = this
               .getInvokeApplicationOutcome();
               this.facesContext.getApplication()
               .getNavigationHandler().handleNavigation(
               this.facesContext, null, outcome);
              
               this.viewId = this.getRenderedViewId();
              
               this.updateConversationId();
              
               AbstractSeamTest.this.phases
               .afterPhase(new PhaseEvent(
               this.facesContext,
               PhaseId.INVOKE_APPLICATION,
               MockLifecycle.INSTANCE));
              
               }
              
               }
              
               }
              
               }
              
               if (this.skipRender()) {
               // we really should look at redirect parameters here!
               } else {
              
               AbstractSeamTest.this.phases.beforePhase(new PhaseEvent(
               this.facesContext, PhaseId.RENDER_RESPONSE,
               MockLifecycle.INSTANCE));
              
               this.renderResponseBegun = true;
              
               this.renderResponse();
              
               this.renderResponseComplete = true;
              
               this.facesContext.getApplication().getStateManager()
               .saveSerializedView(this.facesContext);
              
               this.updateConversationId();
              
               AbstractSeamTest.this.phases.afterPhase(new PhaseEvent(
               this.facesContext, PhaseId.RENDER_RESPONSE,
               MockLifecycle.INSTANCE));
              
               final Map renderedViewRootAttributes = this.facesContext
               .getViewRoot().getAttributes();
               if (renderedViewRootAttributes != null) {
               final Map conversationState = new HashMap();
               conversationState.putAll(renderedViewRootAttributes);
               AbstractSeamTest.this.conversationViewRootAttributes.put(
               this.conversationId, conversationState);
               }
              
               }
              
               this.afterRequest();
              
               return this.conversationId;
               }
              
               private void updateConversationId() {
               this.conversationId = Manager.instance().getCurrentConversationId();
               }
              
               private boolean skipRender() {
               return FacesContext.getCurrentInstance().getResponseComplete();
               }
              
               private boolean skipToRender() {
               return FacesContext.getCurrentInstance().getRenderResponse()
               || FacesContext.getCurrentInstance().getResponseComplete();
               }
              
               protected boolean isInvokeApplicationBegun() {
               return this.invokeApplicationBegun;
               }
              
               protected boolean isInvokeApplicationComplete() {
               return this.invokeApplicationComplete;
               }
              
               protected boolean isRenderResponseBegun() {
               return this.renderResponseBegun;
               }
              
               protected boolean isRenderResponseComplete() {
               return this.renderResponseComplete;
               }
              
               }
              
               public class NonFacesRequest extends Request {
               public NonFacesRequest() {
               }
              
               /**
               * @param viewId
               * the view id to be rendered
               */
               public NonFacesRequest(final String viewId) {
               this.setViewId(viewId);
               }
              
               /**
               * @param viewId
               * the view id to be rendered
               * @param conversationId
               * the conversation id
               */
               public NonFacesRequest(final String viewId, final String conversationId) {
               super(conversationId);
               this.setViewId(viewId);
               }
              
               @Override
               protected final boolean isGetRequest() {
               return true;
               }
              
               @Override
               protected final void applyRequestValues() throws Exception {
               throw new UnsupportedOperationException();
               }
              
               @Override
               protected final void processValidations() throws Exception {
               throw new UnsupportedOperationException();
               }
              
               @Override
               protected final void updateModelValues() throws Exception {
               throw new UnsupportedOperationException();
               }
              
               }
              
               public class FacesRequest extends Request {
              
               public FacesRequest() {
               }
              
               /**
               * @param viewId
               * the view id of the form that was submitted
               */
               public FacesRequest(final String viewId) {
               this.setViewId(viewId);
               }
              
               /**
               * @param viewId
               * the view id of the form that was submitted
               * @param conversationId
               * the conversation id
               */
               public FacesRequest(final String viewId, final String conversationId) {
               super(conversationId);
               this.setViewId(viewId);
               }
              
               @Override
               protected final boolean isGetRequest() {
               return false;
               }
              
               }
              
               @Before
               public void begin() {
               this.session = new MockHttpSession(this.servletContext);
               }
              
               @After
               public void end() {
               if (Contexts.isEventContextActive()) {
               Lifecycle.endRequest(this.externalContext);
               }
               Lifecycle.endSession(this.servletContext, new ServletSessionImpl(
               this.session));
               this.session = null;
               }
              
               /**
               * Create a SeamPhaseListener by default. Override to use one of the other
               * standard Seam phase listeners.
               */
               protected static AbstractSeamPhaseListener createPhaseListener() {
               return new SeamPhaseListener();
               }
              
               @BeforeClass
               public static void init() throws Exception {
               application = new MockApplication();
               application.setStateManager(new SeamStateManager(application
               .getStateManager()));
               application.setNavigationHandler(new SeamNavigationHandler(application
               .getNavigationHandler()));
               // don't need a SeamVariableResolver, because we don't test the view
               phases = createPhaseListener();
              
               servletContext = new MockServletContext();
               initServletContext(servletContext.getInitParameters());
               Lifecycle.setServletContext(servletContext);
               new Initialization(servletContext).create().init();
              
               conversationViewRootAttributes = new HashMap<String, Map>();
               }
              
               @AfterClass
               public static void cleanup() throws Exception {
               if (servletContext != null)
               Lifecycle.endApplication(servletContext);
               externalContext = null;
               conversationViewRootAttributes = null;
               }
              
               /**
               * Override to set up any servlet context attributes.
               */
               public static void initServletContext(final Map initParams) {
               }
              
               protected InitialContext getInitialContext() throws NamingException {
               return Naming.getInitialContext();
               }
              
               protected UserTransaction getUserTransaction() throws NamingException {
               return Transactions.getUserTransaction();
               }
              
               /**
               * Get the value of an object field, by reflection.
               */
               protected Object getField(final Object object, final String fieldName) {
               final Field field = Reflections.getField(object.getClass(), fieldName);
               if (!field.isAccessible()) {
               field.setAccessible(true);
               }
               return Reflections.getAndWrap(field, object);
               }
              
               /**
               * Set the value of an object field, by reflection.
               */
               protected void setField(final Object object, final String fieldName,
               final Object value) {
               final Field field = Reflections.getField(object.getClass(), fieldName);
               if (!field.isAccessible()) {
               field.setAccessible(true);
               }
               Reflections.setAndWrap(field, object, value);
               }
              
              }
              


              • 4. Re: UnitTesting Components
                stu2

                No, I haven't seen examples of the MOCK precendence. But think I remember Gavin describing that it would always be used if it was present. That is, it should be in your classpath during tests, but not when you run it. Seam would always install the component marked with MOCK precedence if it could find it, rather than ones with other (lower) precendences. This seems a little inflexible to me, but I'm not sure if this is correct.

                Regardless, you should take a look at HEAD in CVS. In addition to a ton of refactoring of Seam itself, SeamTest has been refactored to more easily support use in JUnit (Check out BaseSeamTest and the comments in the code and history)