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

Reply via email to