14 Replies Latest reply on Oct 23, 2007 12:00 PM by matt.drees

    SeamTest and expectedExceptions

    gagool

      I want to test if a Seam Session bean component throws a particular Exception, where do I do this?

      Is it possible to use the @Test(expectedExceptions... annotation for this?

      The exceptions does not seem to be propagated into the SeamTest class from the FacesRequest/session bean.

        • 1. Re: SeamTest and expectedExceptions
          gagool

          I just did it like this...

          ...
           @Override
           protected void invokeApplication() throws Exception {
           Exception ex = new Exception();
          
           try {
           invokeMethod("#{registeraccount.register}");
           } catch (Exception e) {
           ex = e;
           }
           assert ex.getCause() instanceof EJBTransactionRolledbackException;
           assert ex.getCause().getCause() instanceof RegisterAccountActionException;
           }
          ...
          


          • 2. Re: SeamTest and expectedExceptions
            gagool

            This isn't working as I expect it.
            If an exception is thrown inside the FacesRequest(){}; and the exception isn't propagated into the test class.
            Tests that should fail due to exceptions are not failing..

            The only way seems to be to create a try/catch-block inside each method of FacesRequest.. but that does not look nice.



            Does not work as I exptected:

            public void test() throws Exception {
            new FacesRequest() {
             @Override
             protected void updateModelValues() throws Exception {
             setValue("#{invalidcomponent.blabla}", "me is monkey. me likes to swing treez.");
             }
            
             }.run();
            }
            



            Does work as exptected but adds much jitter code to the tests:
            public void test() throws Exception {
            new FacesRequest() {
             @Override
             protected void updateModelValues() throws Exception {
             try{
             setValue("#{invalidcomponent.blabla}", "me is monkey. me likes to swing treez.");
             catch(exception e){
             Fail("test failed", e);
             }
             }
            
             }.run();
            }
            



            Is this the intended behaviour?


            • 3. Re: SeamTest and expectedExceptions
              matt.drees

              Yeah, I don't like how the exception filter prevents exceptions (though not errors, like assertion failures) from being propagated outside the run() method. Unfortunately, as I understand it, you can't prevent the exception filter from being installed. If I'm wrong, please, someone correct me.


              Maybe for now a convenient way would be to override the exception filter with a mock-precedence component that does nothing. I haven't tried it though.

              • 4. Re: SeamTest and expectedExceptions
                matt.drees

                Ok, I felt like implementing this. It works well enough for me, anyway. Maybe you can do something similar:

                import static org.jboss.seam.annotations.Install.MOCK;
                
                import java.io.IOException;
                
                import javax.servlet.FilterChain;
                import javax.servlet.ServletException;
                import javax.servlet.ServletRequest;
                import javax.servlet.ServletResponse;
                
                import org.jboss.seam.annotations.Install;
                import org.jboss.seam.annotations.Name;
                import org.jboss.seam.annotations.intercept.BypassInterceptors;
                import org.jboss.seam.annotations.web.Filter;
                
                /**
                 * By default, Seam's exception filter is installed in integration tests. Unfortunately,
                 * if an exception occurs during Request.run(), the exception filter logs it and redirects,
                 * but the exception is not thrown (which is correct for a prod environment, but not what
                 * we want for tests). This filter overrides the default exception filter, and throws
                 * any encountered exceptions. ServletExceptions are unwrapped if the root exception
                 * is a runtime exception.
                 * @author Matthew.Drees
                 *
                 */
                @Name("org.jboss.seam.web.exceptionFilter")
                @Install(precedence = MOCK, classDependencies="javax.faces.context.FacesContext")
                @BypassInterceptors
                @Filter(within="org.jboss.seam.web.ajax4jsfFilter")
                public class ExceptionFilter extends org.jboss.seam.web.ExceptionFilter {
                
                 @Override
                 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                 try {
                 chain.doFilter(request, response);
                 } catch (ServletException se) {
                 //unwrap servletException if we can (we can't rethrow a checked non-servlet exception)
                 Throwable throwable = se.getRootCause();
                 if (throwable instanceof RuntimeException) {
                 RuntimeException runtimeException = (RuntimeException) throwable;
                 throw runtimeException;
                 }
                 throw se;
                 }
                 }
                }
                


                It works well if you don't care what kind of exception is thrown, or if you're looking for a runtime exception. If you're looking for a checked exception, your testcase is somewhat verbose:
                
                import javax.servlet.ServletException;
                
                import org.testng.annotations.Test;
                
                public class ExceptionFilterOverrideTest extends BaseIntegrationTest {
                
                 @Test(expectedExceptions = IllegalStateException.class)
                 public void testRuntimeException() throws Exception {
                 new NonFacesRequest() {
                 @Override
                 protected void renderResponse() throws Exception {
                 throw new IllegalStateException();
                 }
                 }.run();
                 }
                
                 @Test(expectedExceptions = Exception.class)
                 public void testGeneralException() throws Exception {
                 new NonFacesRequest() {
                 @Override
                 protected void renderResponse() throws Exception {
                 throw new IllegalStateException();
                 }
                 }.run();
                 }
                
                 @Test(expectedExceptions = IllegalAccessException.class)
                 public void testCheckedException() throws Exception {
                 new NonFacesRequest() {
                 @Override
                 protected void renderResponse() throws Exception {
                 throw new IllegalAccessException();
                 }
                
                 //this could be pulled into a superclass, if used frequently
                 @Override
                 public String run() throws Exception {
                 try {
                 return super.run();
                 } catch (ServletException se) {
                 handleServletException(se);
                 return null; //nonreachable
                 }
                 }
                
                 }.run();
                 }
                
                
                 private void handleServletException(ServletException se) throws Exception, ServletException {
                 Throwable throwable = se.getRootCause();
                 if (throwable instanceof Exception) {
                 Exception exception = (Exception) throwable;
                 throw exception;
                 }
                 throw se;
                 }
                }
                


                • 5. Re: SeamTest and expectedExceptions
                  gagool

                  I wrote my own exception filter as well..
                  The most important thing for me is that there are no tests that seems to be working but are not due to hidden exceptions..
                  It is my opinion that you would expect the exceptions to be passed on.
                  I would say that this is either a bug in the code or the documentation.

                  • 6. Re: SeamTest and expectedExceptions
                    pmuir

                     

                    Unfortunately, as I understand it, you can't prevent the exception filter from being installed. If I'm wrong, please, someone correct me.[/url]

                    You are right, and Gavin and I have discussed this. I don't think we have a JIRA issue for this?



                    • 7. Re: SeamTest and expectedExceptions
                      matt.drees

                      This one addresses the issue, I think:

                      http://jira.jboss.com/jira/browse/JBSEAM-1190

                      I don't think I understand Gavin's last comment, though.

                      • 8. Re: SeamTest and expectedExceptions
                        pmuir

                        Ah, yes, I talked to Gavin at some point and I agree with him that the semantics are currently correct.

                        i.e. install="false" says (in all place) don't install this implementation of the component. It doesn't say (in any place) don't install this and any lower precedence versions of this component.

                        Thus we need something like

                        e.g.

                        <web:ajax4jsf-filter disable="true" />


                        which essentially sets install="false" on all components with this name. The implementation for this is easy enough, but I don't like the semantics of the above - I mean normally you put a definition in components.xml to install/configure a component, not to disable it...

                        So, thinking caps on ;)

                        • 9. Re: SeamTest and expectedExceptions
                          pmuir

                          I created http://jira.jboss.com/jira/browse/JBSEAM-2036 as the old one was really a bug report and this issue isn't a bug, it's a FR.

                          • 10. Re: SeamTest and expectedExceptions
                            matt.drees

                             

                            "pete.muir@jboss.org" wrote:

                            i.e. install="false" says (in all place) don't install this implementation of the component. It doesn't say (in any place) don't install this and any lower precedence versions of this component.


                            So, this is what I hear you saying:

                            <my:component-class installed="true" my-property="me"/>

                            means "Go find the class called ComponentClass, and make sure it's going to be installed. In addition, when instantiating, set myProperty to "me"."

                            However,
                            <my:component-class installed="false" my-property="me"/>

                            means "See this xml element? Ignore it. Pretend it doesn't exist. In particular, don't set myProperty to "me"."

                            Am I hearing you right?
                            If so, then I'd say two things.
                            First, it's unintuitive. "installed='true'" is describing the class, while "installed='false'" is describing this xml element.

                            Second, I don't think Initialization.java actually implements this. I haven't tested this, so don't quote me, but the code looks pretty clear. The "installed" variable defined on/near line 324 isn't used to determine whether the properties are set or not. So, in the above example, both xml declarations result in myProperty being set to "me".

                            Please, clarify. I may be missing something.

                            • 11. Re: SeamTest and expectedExceptions
                              pmuir

                              Sorry for the delay,

                              let me try to get this right (its not trivial)

                              So, this is what I hear you saying:

                              <my:component-class installed="true" my-property="me"/>

                              means "Go find the class called ComponentClass, and make sure it's going to be installed. In addition, when instantiating, set myProperty to "me"."


                              Well, actually the namespacing rules are more complex than this. It actually says (I hope ;)

                              1) Imply the class name from the namespace plus un-camel-cased xml element name

                              2) if name isn't in specified, get the name from the @Name annotation on the class

                              3) If a name already exists, only configure, don't install a copy (assuming @Install is set to true or not specified)

                              4) Otherwise, imply the name from the xml-element name

                              5) if installed="true" or not specified then mark the xml element as used

                              However,
                              <my:component-class installed="false" my-property="me"/>

                              means "See this xml element? Ignore it. Pretend it doesn't exist. In particular, don't set myProperty to "me"."


                              Yes, essentially.

                              First, it's unintuitive. "installed='true'" is describing the class, while "installed='false'" is describing this xml element.


                              Yes, its unintuitive. However, installed="true" is, in both cases, describing the xml element (it's just that in some cases the xml element's presence causes a component to be installed - does that make sense from the above list of install proceedure?).

                              Second, I don't think Initialization.java actually implements this. I haven't tested this, so don't quote me, but the code looks pretty clear. The "installed" variable defined on/near line 324 isn't used to determine whether the properties are set or not. So, in the above example, both xml declarations result in myProperty being set to "me".


                              No, what it does is control whether this xml element does just configuration or whether it actually installs a component as well (I think, but I'm tired). I.e. if the component isn't installed in code @Install(false) then the xml element can install one or not depending on the value of installed.

                              Let me know if this makes more sense.

                              • 12. Re: SeamTest and expectedExceptions
                                matt.drees

                                So I think I can say I understand what Seam does now. I don't necessarily agree with it, though. I do understand that the installed attribute can't just simply override the annotation on that class, otherwise something like

                                <core:jbpm/>
                                
                                wouldn't work, because "installed" would still be false.

                                On a related note, I did a little testing to see if I was reading the source code right. The results are a little odd, I think:

                                <?xml version="1.0" encoding="UTF-8"?>
                                <components xmlns="http://jboss.com/products/seam/components"
                                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                 xsi:schemaLocation="http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.0.xsd">
                                
                                 <!-- A standard @Name-annotated component: -->
                                 <component name="foo" scope="session" startup="true"> <!-- scope etc attributes ignored... -->
                                 <property name="title">A Foo</property> <!-- ...but property configurations are not... -->
                                 </component>
                                 <component name="foo" installed="false">
                                 <property name="description">installed by default</property> <!-- ...even when installed="false" -->
                                 </component>
                                
                                 <!-- Not @Name-annotated: -->
                                 <component name="bar" class="eg.AbstractExampleComponent$Bar" scope="session" startup="true"> <!-- scope etc attributes not ignored -->
                                 <property name="title">A Bar</property> <!-- properties not ignored, either, of course -->
                                 </component>
                                 <component name="bar">
                                 <property name="description">not annotated as a component</property> <!-- (ever) -->
                                 </component>
                                
                                 <!-- @Name-annotated, but also @Install(false): -->
                                 <component class="eg.AbstractExampleComponent$Baz" scope="session" startup="true"> <!-- scope etc attributes not ignored -->
                                 <property name="title">A Baz</property>
                                 <property name="description">a non-installed component</property>
                                 </component>
                                
                                </components>
                                


                                package eg;
                                
                                import org.jboss.seam.ScopeType;
                                import org.jboss.seam.annotations.Install;
                                import org.jboss.seam.annotations.Name;
                                import org.jboss.seam.annotations.Scope;
                                
                                
                                public class AbstractExampleComponent {
                                
                                 private String description;
                                 private String title;
                                
                                 public String getDescription() {
                                 return description;
                                 }
                                 public void setDescription(String name) {
                                 this.description = name;
                                 }
                                 public String getTitle() {
                                 return title;
                                 }
                                 public void setTitle(String title) {
                                 this.title = title;
                                 }
                                
                                 @Name("foo")
                                 @Scope(ScopeType.CONVERSATION)
                                 public static class Foo extends AbstractExampleComponent {}
                                
                                 @Scope(ScopeType.CONVERSATION)
                                 public static class Bar extends AbstractExampleComponent {}
                                
                                 @Name("baz")
                                 @Install(false)
                                 @Scope(ScopeType.CONVERSATION)
                                 public static class Baz extends AbstractExampleComponent {}
                                
                                }
                                


                                package eg;
                                
                                import org.jboss.seam.Component;
                                import org.jboss.seam.ScopeType;
                                import org.jboss.seam.Seam;
                                import org.jboss.seam.mock.SeamTest;
                                import org.testng.annotations.Test;
                                
                                import eg.AbstractExampleComponent.Bar;
                                import eg.AbstractExampleComponent.Baz;
                                import eg.AbstractExampleComponent.Foo;
                                
                                public class InitializationTest extends SeamTest{
                                
                                 @Override
                                 protected void startJbossEmbeddedIfNecessary() throws Exception {
                                 }
                                
                                 @Test
                                 public void foo() throws Exception {
                                 new ComponentTest() {
                                
                                 @Override
                                 protected void testComponents() throws Exception {
                                 Foo foo = (Foo) Component.getInstance("foo");
                                 assert foo.getTitle().equals("A Foo");
                                 assert foo.getDescription().equals("installed by default");
                                 Component component = Seam.componentForName("foo");
                                 assert component.getScope() == ScopeType.CONVERSATION; //xml override not used
                                 assert component.isStartup() == false; //same
                                 }
                                
                                 }.run();
                                 }
                                
                                 @Test
                                 public void bar() throws Exception {
                                 new ComponentTest() {
                                
                                 @Override
                                 protected void testComponents() throws Exception {
                                 Bar bar = (Bar) Component.getInstance("bar");
                                 assert bar.getTitle().equals("A Bar");
                                 assert bar.getDescription().equals("not annotated as a component");
                                 Component component = Seam.componentForName("bar");
                                 assert component.getScope() == ScopeType.SESSION;
                                 assert component.isStartup() == true;
                                 }
                                
                                 }.run();
                                 }
                                 @Test
                                 public void baz() throws Exception {
                                 new ComponentTest() {
                                
                                 @Override
                                 protected void testComponents() throws Exception {
                                 Baz baz = (Baz) Component.getInstance("baz");
                                 assert baz.getTitle().equals("A Baz");
                                 assert baz.getDescription().equals("a non-installed component");
                                 Component component = Seam.componentForName("baz");
                                 assert component.getScope() == ScopeType.SESSION;
                                 assert component.isStartup() == true;
                                 }
                                
                                 }.run();
                                 }
                                
                                }
                                


                                (The test passes)

                                The biggest oddity to me is that for installed components, you can't override *any* annotations, not just "@Install". Another oddity is that for install="false" elements, properties are still picked up and used.

                                It smells buggy. Or at least not-thought-through.
                                Is this the way things are supposed to be?

                                • 13. Re: SeamTest and expectedExceptions
                                  pmuir

                                  Matt, lets work through this on the JIRA issue for 2.0.1.

                                  • 14. Re: SeamTest and expectedExceptions
                                    matt.drees

                                    Sounds good. It doesn't seem to be a must-have for 2.0