Hello Leonardo, This sounds very cool. Why not add it to MyFaces-Test instead?
On Saturday, January 25, 2014, Leonardo Uribe <[email protected]> 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://w <http://blogs.jsfcentral.com/JSFNewscast/>ww.enterprisejavanews.com <http://ww.enterprisejavanews.com>* * JSFCentral Interviews Podcast: http://www.jsfcentral.com/resources/jsfcentralpodcasts/ * Sign up for the JSFCentral Newsletter: http://oi.vresp.com/?fid=ac048d0e17
