myfaces-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Leonardo Uribe <lu4...@gmail.com>
Subject Re: [core][mftest][discuss] Create new module for JUnit Mock Testing using MyFaces Core, MyFaces Test and CDI (OWB)
Date Fri, 31 Jan 2014 19:19:22 GMT
Hi

Trying to improve this stuff I finally found a way to make a junit
runner. Look at this example that summarizes the possible syntax:

@RunWith(MyFacesTestRunner.class)
@TestConfig(
    scanAnnotations = true,
    oamAnnotationScanPackages = "my.custom.testpackage",
    webappResourcePath = "webapp",
    expressionFactory = "com.sun.el.ExpressionFactoryImpl")
@TestServletListeners({
    "org.apache.webbeans.servlet.WebBeansConfigurationListener"
})
@DeclareFacesConfig("/test-faces-config.xml")
public class MyFacesRunnerSimpleTestCase
{
    @TestContainer
    private MyFacesContainer container;

    @Inject
    @Named("helloWorld")
    private HelloWorldController helloWorldBean;

    @BeforeRequest
    public void doFilterStart()
    {
        System.out.println("Filter start...");
    }

    @AfterRequest
    public void doFilterEnd()
    {
        System.out.println("Filter end...");
    }

    @Test
    public void testHelloWorld() throws Exception
    {
        container.startViewRequest("/helloWorld.xhtml");
        container.processLifecycleExecute();
        Assert.assertEquals("page2.xhtml", helloWorldBean.send());
        container.renderResponse();

        container.getClient().inputText("mainForm:name", "John Smith");
        // The button end current request and start a new request
        // with a simulated submit
        container.getClient().submit("mainForm:submit");

        container.processLifecycleExecute();
        Assert.assertEquals("John Smith", helloWorldBean.getName());
        Assert.assertEquals("/page2.xhtml",
            container.getFacesContext().getViewRoot().getViewId());
        container.endRequest();
    }

}

Instead make an specific JSF-CDI runner, I used the SPI stuff we
already have in place to make a JSF JUnit runner that use
InjectionProvider spi to do the injection stuff over the test
instance. I also introduced a new annotation called @TestConfig to
declare the test configuration and another annotation
@TestServletListeners to register a Servlet Listener into the runner.
For example, to make it work with OWB, you only need to register the
listener and that's it. If you want to try Weld, nothing more simple,
just register its listener and that's it. I think it will also work
with Spring.

The advantage of use an annotation for configuration and another
annotation for register servlet listeners is that you can modify the
configuration just extending the class and overriding the annotation
with a new configuration. I'm still thinking about how to to load a
servlet listener and in that way the configuration by default using
something similar to SPI but using a resource in the classpath. For
example, a simple text file in
META-INF/services/org.apache.myfaces.mc.test.core.runner.ServletListeners
with the servlet listener class names to load for MyFacesTestRunner.

This is still work in progress, but any suggestion about how to
improve the syntax is most welcome.

regards,

Leonardo Uribe

2014-01-25 Leonardo Uribe <lu4242@gmail.com>:
> Hello Kito
>
> It is not possible to include it into MyFaces-Test by several reasons:
>
> - MyFaces Core has a dependency over MyFaces Test. This code is in
> between both projects, so if we include it into MyFaces Test it means
> we will have a circular dependency between both projects.
> - The code includes some small but important changes for MyFaces Core.
> It is easier to maintain these details if the resulting module is
> released along with MyFaces Core, and it will be easier for users to
> know which version of the module is compatible with, because the
> released module will have the same release version numbers as MyFaces
> Core.
> - This code is needed to test Myfaces Core code, because we have
> already a bunch of test cases using this stuff.
>
> The code has been used for some time, and the idea is improve it and
> give the opportunity to users to include it into their projects, and
> also use it in other MyFaces projects in the future.
>
> regards,
>
> Leonardo
>
>
> 2014-01-25 Kito Mann <kito.mann@virtua.com>:
>> Hello Leonardo,
>>
>> This sounds very cool. Why not add it to MyFaces-Test instead?
>>
>>
>> On Saturday, January 25, 2014, Leonardo Uribe <lu4242@gmail.com> wrote:
>>>
>>> Hi
>>>
>>> With JSF 2.0/2.1 and with the introduction of JSF 2.2, it has become more
>>> and
>>> more frequent to find cases where you have a JSF-CDI application, and you
>>> want
>>> to create JUnit tests. Usually, the interest in these cases is test some
>>> complex server side logic, but the problem is you usually need some
>>> control of
>>> the JSF lifecycle, or there is an interaction between pages and beans and
>>> with a mocked environment like the one provided in MyFaces Test you can
>>> only
>>> simulate partially the beans. In these cases, it is not necessary to fully
>>> simulate the client, because what you really want to check is what's going
>>> on
>>> in the server side.
>>>
>>> Solutions like the one provided by JSFUnit or Arquillian does not fit
>>> properly
>>> in these cases, because the server does not run on the same side as the
>>> client,
>>> so there are 2 running classloaders (the one where junit is and the other
>>> that belongs to the web server) which makes debugging difficult.
>>>
>>> Additionally, some time ago, these issues were opened in MYFACESTEST issue
>>> tracker:
>>>
>>> - MYFACESTEST-42 Implement support for creating managed beans from
>>>                  faces-config.xml or other JSF config files.
>>> - MYFACESTEST-59 Move MockViewDeclarationLanguageFactory from MyFaces Core
>>> to
>>>                  MyFaces-test
>>> - MYFACESTEST-62 Move FaceletTestCase from internal MyFaces to the MyFaces
>>>                  Test project
>>>
>>> These issues suggest it would be great to have some mock test environment
>>> that
>>> can do things like build a view from a .xhml or read faces-config.xml or
>>> .taglib.xml files. In few words, run MyFaces Core in a JUnit test.
>>>
>>> Looking for a way to fix this problem, some time ago it was created this
>>> issue:
>>>
>>> https://issues.apache.org/jira/browse/MYFACES-3376 Create abstract test
>>> classes
>>>                                         that runs MyFaces Core as in a
>>> container
>>>
>>> Inside MyFaces Core Impl module, in src/test/java directory there is a
>>> package
>>> called org.apache.myfaces.mc.test.core with these junit base test classes:
>>>
>>> - AbstractMyFacesTestCase
>>> - AbstractMyFacesRequestTestCase
>>> - AbstractMyFacesFaceletsTestCase
>>> - AbstractMyFacesCDIRequestTestCase
>>>
>>> These classes creates a mock servlet test environment that is able to run
>>> MyFaces Core using JUnit. For example:
>>>
>>> // 1. In pom.xml it is necessary to make available src/main/webapp
>>> resources
>>> // as test resources:
>>>
>>> <build>
>>>     <testResources>
>>>         <testResource>
>>>             <directory>${project.basedir}/src/test/resources</directory>
>>>         </testResource>
>>>         <testResource>
>>>             <directory>${project.basedir}/src/main/webapp</directory>
>>>             <targetPath>webapp</targetPath>
>>>         </testResource>
>>>     </testResources>
>>>     <!-- .... -->
>>>
>>> // 2. Add beans.xml in src/main/java/META-INF and src/test/java/META-INF
>>> // 3. Now create the test class
>>>
>>> public class SimpleTestCase extends AbstractMyFacesCDIRequestTestCase
>>> {
>>>     @Inject
>>>     @Named("helloWorld")
>>>     private HelloWorldController helloWorldBean;
>>>
>>>     @Test
>>>     public void testHelloWorld() throws Exception
>>>     {
>>>         startViewRequest("/helloWorld.xhtml");
>>>         processLifecycleExecute();
>>>         Assert.assertEquals("page2.xhtml", helloWorldBean.send());
>>>         renderResponse();
>>>
>>>         client.inputText("mainForm:name", "John Smith");
>>>         // The button end current request and start a new request
>>>         // with a simulated submit
>>>         client.submit("mainForm:submit");
>>>
>>>         processLifecycleExecute();
>>>         Assert.assertEquals("John Smith", helloWorldBean.getName());
>>>         Assert.assertEquals("/page2.xhtml",
>>>                          facesContext.getViewRoot().getViewId());
>>>         endRequest();
>>>     }
>>>
>>>     @Override
>>>     protected ExpressionFactory createExpressionFactory()
>>>     {
>>>         // By default it uses a mock ELFactory.
>>>         return new com.sun.el.ExpressionFactoryImpl();
>>>     }
>>>
>>>     @Override
>>>     protected String getWebappContextFilePath()
>>>     {
>>>         // By default it is the package name of the test.
>>>         return "webapp";
>>>     }
>>>
>>> }
>>>
>>> The example simulates a helloworld submit. getWebappContextFilePath()
>>> defines
>>> the link between the webapp context as test resource, to allow the test to
>>> load the resources from that location. All faces-config.xml and
>>> .taglib.xml
>>> from the classpath are automatically loaded. JSF annotation scanning is
>>> disabled by default but you can enable it overriding isScanAnnotations()
>>> method and setting up "org.apache.myfaces.annotation.SCAN_PACKAGES" param
>>> to reduce the time spent in classpath scanning.
>>>
>>> This code has allowed us to make very complex tests inside MyFaces Core
>>> very
>>> easily, like Faces Flows, Resource Library Contracts, Reset Values or View
>>> Pooling. The resulting simulated environment is almost identical in
>>> comparison
>>> with the one created inside a web server, and the differences can be fixed
>>> quite easily, overriding the appropiate methods. The integration with
>>> CDI is just register the servlet listener and that's it.
>>>
>>> If users are using some JSF third-party component library, it is quite
>>> easy
>>> to create a custom mock client and use Firebug or something else to check
>>> the http requests and provide some methods to fill the simulated client
>>> side logic.
>>>
>>> Create test cases is pretty straightforward, because everything is running
>>> in the junit test case, so you don't need to write any callback, just
>>> write
>>> the instructions and do the necessary validations in the right spots. This
>>> is
>>> how a redirect is simulated:
>>>
>>>     @Test
>>>     public void testRedirect1() throws Exception
>>>     {
>>>         startViewRequest("/redirect1.xhtml");
>>>         processLifecycleExecute();
>>>         renderResponse();
>>>         client.submit("mainForm:submit");
>>>         processLifecycleExecuteAndRender();
>>>         // redirect sends 302 response, so the client must take it and
>>>         // start the redirected request
>>>         client.processRedirect();
>>>         // this is the lifecycle of the redirected request.
>>>         processLifecycleExecuteAndRender();
>>>         String redirectedContent = getRenderedContent();
>>>         Assert.assertTrue(redirectedContent.contains("Redirected Page"));
>>>     }
>>>
>>> The code tries to reuse as much configuration info as possible. For
>>> example
>>> a test case like FlowMyFacesRequestTestCase in my machine in Netbeans
>>> takes 2.5 seconds to be executed and then 0.17 seconds per each additional
>>> test, and the IDE takes another 3 or 4 seconds to execute
>>> "compile on save" and start junit. CDI takes about 1.1 seconds per method.
>>> Note this is a lot less than deploy a server like jetty or tomee, which in
>>> the same conditions could take (with a helloworld app) about 10 seconds or
>>> more to start, run the test and stop.
>>>
>>> The proposal I have for the consideration of MyFaces community is create a
>>> new module for MyFaces Core 2.2 branch called impl-test. The module takes
>>> the code from org.apache.myfaces.mc.test.core in impl module and repackage
>>> it into a new jar file so users can reference it into its own projects.
>>> In that way we can use the code in MyFaces Core like we are doing right
>>> now and we can maintain it at the same time.
>>>
>>> This is the issue in jira to keep track of this feature:
>>>
>>> https://issues.apache.org/jira/browse/MYFACES-3849
>>>
>>> I have already committed the necessary code so you can just take it from
>>> trunk and try it. If no objections, I'll include the module into the next
>>> release of MyFaces Core. This is also a good moment to provide ideas about
>>> how to improve this feature, so suggestions are welcomed.
>>>
>>> regards,
>>>
>>> Leonardo Uribe
>>
>>
>>
>> --
>> ___
>>
>> Kito D. Mann | @kito99 | Author, JSF in Action
>> Virtua, Inc. | http://www.virtua.com | JSF/Java EE training and consulting
>> http://www.JSFCentral.com | @jsfcentral
>> +1 203-998-0403
>>
>> * Listen to the Enterprise Java Newscast: http://www.enterprisejavanews.com
>> * JSFCentral Interviews Podcast:
>> http://www.jsfcentral.com/resources/jsfcentralpodcasts/
>> * Sign up for the JSFCentral Newsletter: http://oi.vresp.com/?fid=ac048d0e17
>>

Mime
View raw message