JUnit test Presenter versus NavigationManager

49 views
Skip to first unread message

rymdbullen

unread,
Feb 8, 2012, 11:35:47 AM2/8/12
to jfxflow-discuss
I am successful in testing the Presenter but when I try to run the
NavigationManager I get the dreaded IllegalStateException: Task must
only be used from the FX Application Thread.

I am using the BackgroundTask and the TaskExecutor that does not make
use of threading, as you outlined in the post in the JavaFX forum
https://forums.oracle.com/forums/message.jspa?messageID=10126950#10126950.

Whats the difference JUnit testing a presenter versus a
NavigationManager?


Daniel Zwolenski

unread,
Feb 8, 2012, 2:58:59 PM2/8/12
to jfxflow...@googlegroups.com
This is probably a little dependent on the code you are testing. The NavManager itself doesn't use any Tasks or threads, most likely it is something (in your app I'd guess) attached to it that is then starting a thread.

Can you give a little more detail on your test, how it wires stuff up and what the code being tested does?

If it helps NavManager is an interface so you can mock it if you need to, but I'm not sure that will solve your problem here.

rymdbullen

unread,
Feb 9, 2012, 5:10:29 AM2/9/12
to jfxflow-discuss
I'm setting up the Navigator test by instatiating a JFX Flow
AppFactory, just like the example app First Contact. The AppFactory is
setting up the FXML, NavManager and related services.




Excerpt of the MyAppFactory:




            detailUserPresenter = loader.load("/fxml/DetailUser.fxml",
resourceBundle);
            detailUserPresenter.setUserService(getUserService());
           
detailUserPresenter.setNavigationManager(navigationManager);
            detailUserPresenter.setErrorHandler(getErrorHandler());
            detailUserPresenter.setTaskExecutor(taskExecutor);








This is an excerpt of the JUnit test:




    @Test
    public void testNavigation()
    {
        MyAppFactory myAppFactory = MyAppFactory.getInstance();
        myAppFactory.setUserService(userService);
        taskExecutor = new NoThreadTaskExecutor();
       
myAppFactory.getUserDetailPresenter().setTaskExecutor(taskExecutor);

        NavigationManager navigationManager =
myAppFactory.getNavigationManager();




        Place placeLogin = new Place("login");
        navigationManager.goTo(placeLogin);
        // asserts go here
        ...




        Place placeDetailUser = new PlaceBuilder("detailUser")
                .parameter("userId", 1L)
                .parameter("isAdmin", Boolean.FALSE)
                .build();
        // asserts go here
       ...




When DetailUserPresenter is "activated" it tries to create a
BackgroundTask as described below and I get the IllegalStateException.




        final BackgroundTask<User> loadTask = new
BackgroundTask<User>()
        {
            @Override
            public User call() throws Exception
            {
                return userService.getUser(userId);
            }
        };




JavaFX runtime does not like me creating a Task running headless...
this is regardless of using your example of TestTaskExecutor.




Best Regards!








On Feb 8, 8:58 pm, Daniel Zwolenski <zon...@googlemail.com> wrote:
> This is probably a little dependent on the code you are testing. The NavManager itself doesn't use any Tasks or threads, most likely it is something (in your app I'd guess) attached to it that is then starting a thread.
>
> Can you give a little more detail on your test, how it wires stuff up and what the code being tested does?
>
> If it helps NavManager is an interface so you can mock it if you need to, but I'm not sure that will solve your problem here.
>
> On 09/02/2012, at 2:35 AM, rymdbullen <jan.stenv...@gmail.com> wrote:
>
>
>
>
>
>
>
> > I am successful in testing the Presenter but when I try to run the
> > NavigationManager I get the dreaded IllegalStateException: Task must
> > only be used from the FX Application Thread.
>
> > I am using the BackgroundTask and the TaskExecutor that does not make
> > use of threading, as you outlined in the post in the JavaFX forum
> >https://forums.oracle.com/forums/message.jspa?messageID=10126950#1012....

Daniel Zwolenski

unread,
Feb 9, 2012, 7:31:15 AM2/9/12
to jfxflow...@googlegroups.com
How do you handle the result of your Task execution? Do you call task.getValue(), getException(), or anything similar? These will fail if not on the GUI thread from the looks of it (I'm undecided whether this is a good thing or not). If you use the onSuccess method setup that I outlined you should be able to avoid calling these thread-protected methods.

If you don't call any of the Task methods directly, then my next guess would be that the task executor being used may not be the 'test' one and may still be the original, proper one. Try adding some logging (or break points) to your test task executor and make sure it is the one being called. Add some logging to your normal TaskExecutor as well and make sure that is not being executed. 

Failing all that you could also use an interface for your BackgroundTask instead of making it extend Task directly. In this case you would then just wrap the call in a JFX Task in your DefaultTaskExecutor and delegate to the methods on the interface. Then in your Test executor you are absolutely guaranteed to have no JFX task stuff involved in your test. 

For the record you could test your Activity by calling the activate method directly yourself. This would be a more pure unit test as it would only be testing that part of the code, whereas using the NavManager you are testing across a few bits of code. I can however understand a desire to test the wiring up of the NavManager and making sure that your Activity gets activated properly, and technically there should be no problem doing what you are trying to do.

Let me know how you get on, 
Dan

rymdbullen

unread,
Feb 9, 2012, 10:17:34 AM2/9/12
to jfxflow-discuss
The problem is the use of the stateProperty() method in the
BackgroundTask. When I add a listener to the stateProperty I am
calling the Task method.

protected BackgroundTask()
{
stateProperty().addListener(new ChangeListener<State>()
{
@Override
public void changed(ObservableValue<? extends State>
source, State oldState, State newState)
{
if (newState.equals(State.SUCCEEDED))
{
onSuccess(getValue());
}
else if (newState.equals(State.FAILED))
{
onError(getException());
}
}
});

So, is there a way to simulate the stateProperty() method to get the
result of the task?

Best Regards

Daniel Zwolenski

unread,
Feb 9, 2012, 3:34:40 PM2/9/12
to jfxflow...@googlegroups.com
Ah, yes, that'd be it. I should probably have actually run that code and tested it - sorry about that!

I've posted some options to the forum topic for this: https://forums.oracle.com/forums/thread.jspa?messageID=10138914

rymdbullen

unread,
Feb 10, 2012, 5:50:52 AM2/10/12
to jfxflow-discuss
This does the trick. Thank you very much!

This brought me farther on my way to test the complete flow. However I
got another error when running the AppFactory test.

The sequence I'm running in the test mimics the AppFactory test in JFX
Flow. I send parameters to a page, performs the transistion with
goTo(). This all evaluates ok. However, when I trigger a goBack() in
the navigation manager the javafx runtime cries:


Place placeDetailUser = new PlaceBuilder("detailUser")
.parameter("userId", 1L)
.parameter("isAdmin", Boolean.FALSE)
.build();
navigationManager.goTo(placeDetailUser);
// assertions go here


navigationManager.goBack(); /* exception happens here */



java.lang.IllegalArgumentException: Children: duplicate children added
parent=StackPane@888e6c[styleClass=child-area]
at javafx.scene.Parent$1.onProposedChange(Unknown Source)
at com.sun.javafx.collections.VetoableObservableList.add(Unknown
Source)
at com.sun.javafx.collections.ObservableListWrapper.add(Unknown
Source)
at
com.zenjava.jfxflow.transition.DefaultTransitionFactory.createTransition(DefaultTransitionFactory.java:
50)
at com.zenjava.jfxflow.actvity.ParentActivity
$CurrentActivityListener.changed(ParentActivity.java:167)
at com.zenjava.jfxflow.actvity.ParentActivity
$CurrentActivityListener.changed(ParentActivity.java:139)
at com.sun.javafx.binding.ExpressionHelper
$SingleChange.fireValueChangedEvent(Unknown Source)
at
com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(Unknown
Source)
at
javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(Unknown
Source)
at javafx.beans.property.ObjectPropertyBase.markInvalid(Unknown
Source)
at javafx.beans.property.ObjectPropertyBase.set(Unknown Source)
at com.zenjava.jfxflow.actvity.ParentActivity
$CurrentPlaceListener.changed(ParentActivity.java:126)
at com.zenjava.jfxflow.actvity.ParentActivity
$CurrentPlaceListener.changed(ParentActivity.java:114)
at com.sun.javafx.binding.ExpressionHelper
$SingleChange.fireValueChangedEvent(Unknown Source)
at
com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(Unknown
Source)
at
javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(Unknown
Source)
at javafx.beans.property.ObjectPropertyBase.markInvalid(Unknown
Source)
at javafx.beans.property.ObjectPropertyBase.access$100(Unknown
Source)
at javafx.beans.property.ObjectPropertyBase
$Listener.invalidated(Unknown Source)
at com.sun.javafx.binding.ExpressionHelper
$SingleInvalidation.fireValueChangedEvent(Unknown Source)
at
com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(Unknown
Source)
at
javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(Unknown
Source)
at javafx.beans.property.ObjectPropertyBase.markInvalid(Unknown
Source)
at javafx.beans.property.ObjectPropertyBase.set(Unknown Source)
at
com.zenjava.jfxflow.navigation.DefaultNavigationManager.goBack(DefaultNavigationManager.java:
48)
at TestMyAppFactory.testNavigation(TestMyAppFactory.java:90)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:
39)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:
25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod
$1.runReflectiveCall(FrameworkMethod.java:45)
at
org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:
15)
at
org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:
42)
at
org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:
20)
at
org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:
28)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:
68)
at
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:
47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at
org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:
28)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at
org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:
62)
at
org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.executeTestSet(AbstractDirectoryTestSuite.java:
140)
at
org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.execute(AbstractDirectoryTestSuite.java:
127)
at org.apache.maven.surefire.Surefire.run(Surefire.java:177)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:
39)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:
25)
at java.lang.reflect.Method.invoke(Method.java:597)
at
org.apache.maven.surefire.booter.SurefireBooter.runSuitesInProcess(SurefireBooter.java:
345)
at
org.apache.maven.surefire.booter.SurefireBooter.main(SurefireBooter.java:
1009)

If I execute the vanilla JFX Flow TestDefaultNavigationManager, all is
nice and dandy.

Best Regards

Daniel Zwolenski

unread,
Feb 12, 2012, 2:09:09 AM2/12/12
to jfxflow...@googlegroups.com
This problem would be caused by the animated page transitions in JFX Flow. As you move from page to page a short animation is run that removes the old page and adds the new one. The default animation is a fade-out/fade-in. 

The flow is something like this: 

1. Make new page invisible (opacity = 0)
2. Add new page to StackPane
3. Run fade-out animation on old page (animate opacity from 1 to 0)
4. Run fade-in animation on new page (animate opacity from 0 to 1)
5. Remove old page from StackPane

When you are running in code, you are going 'back' to the old page before the above flow has finished, which means that the old page is still currently a child of the stack. By going back to it, it is re-added (as it is now the 'new page') and you see the resulting exception. To stop this from happening in normal usage, JFX Flow blocks while the animation is happening. This stops you from doing anything (like hitting the back button) until the process is fully complete, sidestepping the whole problem. There's nothing stopping you from doing this at a code level however. 

If you want to run this as part of your unit test, you will want to turn off transitions. Stupidly it looks like I forgot to add a parameter to the Browser to turn off transitions all together but you can provide your own 'empty' transition factory (as I mentioned in an email recently, I'm not super happy with my transition API and intend to have another look at it fairly soon). 

In your application context, try creating your browser like so and let me know if this works for you:

Browser browser = new Browser(new DefaultNavigationManager(), new NoAnimationTransitionFactory(), "Your Title");

With this: 

public class NoAnimationTransitionFactory implements TransitionFactory
{
    public Animation createTransition(final StackPane container, final Activity oldActivity, final Activity newActivity)
    {
        Animation animation = new PauseTransition(Duration.millis(0));
        animation.setOnFinished(new EventHandler<ActionEvent>()
        {
            public void handle(ActionEvent event)
            {
                if (oldActivity != null)
                {
                    container.getChildren().remove(oldActivity.getView().toNode());
                }

                if (newActivity != null)
                {
                    container.getChildren().add(newActivity.getView().toNode());
                }
            }
        });
        return animation;
    }
}

Not as clean as I'd like, but it might work for you (unless the underlying jfx implementation pause animation has some threading gotchas, but let me know if this doesn't work and we'll see if we can do better). I'll look at how I can clean this up in a future release.  

Cheers, 
Dan
Reply all
Reply to author
Forward
0 new messages