Skip to content

Commit

Permalink
Merge pull request #266 from sialcasa/263_publish_testable
Browse files Browse the repository at this point in the history
#263 publish testable
  • Loading branch information
manuel-mauky committed Jun 15, 2015
2 parents e925715 + aa2a3e3 commit 7290ee9
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 5 deletions.
13 changes: 11 additions & 2 deletions mvvmfx/src/main/java/de/saxsys/mvvmfx/ViewModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
******************************************************************************/
package de.saxsys.mvvmfx;

import de.saxsys.mvvmfx.utils.notifications.NotificationTestHelper;
import javafx.application.Platform;
import de.saxsys.mvvmfx.internal.viewloader.View;
import de.saxsys.mvvmfx.utils.notifications.NotificationCenter;
Expand Down Expand Up @@ -45,8 +46,16 @@
public interface ViewModel {

/**
* Publishes a notification to the subscribers of the notificationId. This notification will be send to the
* UI-Thread.
* Publishes a notification to the subscribers of the messageName. This notification will be send to the
* UI-Thread.
* <p>
*
* This notification mechanism uses the {@link NotificationCenter} internally with the difference that messages send
* by this method aren't globally available. Instead they can only be received by this viewModels {@link #subscribe(String, NotificationObserver)}
* method or when using this viewModel instance as argument to the {@link NotificationCenter#subscribe(ViewModel, String, NotificationObserver)} method.
* <p>
*
* See {@link NotificationTestHelper} for a utility that's purpose is to simplify unit tests with notifications.
*
* @param messageName
* of the notification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
*
* {@code Object[]} with a notification.
*
* <p>
*
* There is a util {@link NotificationTestHelper} that's purpose is to simplify testing of notifications in Unit-Tests.
*
*
* @author sialcasa
*
*/
Expand Down Expand Up @@ -99,7 +104,7 @@ void unsubscribe(String messageName,
* @param observer
* which should execute when the notification occurs
*/
void subscribe(ViewModel view, String messageName,
void subscribe(ViewModel viewModel, String messageName,
NotificationObserver observer);

/**
Expand All @@ -118,7 +123,7 @@ void unsubscribe(ViewModel viewModel, String messageName,
* @param viewModel
* @param observer
*/
void unsubscribe(ViewModel view,
void unsubscribe(ViewModel viewModel,
NotificationObserver observer);

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ public interface NotificationObserver {
* @param payload
* which are passed
*/
public void receivedNotification(String key, Object... payload);
void receivedNotification(String key, Object... payload);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package de.saxsys.mvvmfx.utils.notifications;

import de.saxsys.mvvmfx.ViewModel;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.util.Pair;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
* The {@link NotificationTestHelper} is used to simplify the testing of
* notifications. It is especially useful when notifications are send from
* different thread and when testing the direct notification between a viewModel and the View
* (via {@link ViewModel#publish(String, Object...)} and {@link ViewModel#subscribe(String, NotificationObserver)})
* <p>
* This class implements {@link NotificationObserver} and therefore can be added as subscriber. It will record
* every received notification and can be tested afterwards.
* <p>
*
* The {@link ViewModel#publish(String, Object...)} method will send all notifications on the JavaFX UI thread.
* Therefore when testing the publishing of notifications JavaFX has to be running which isn't the case
* with plain JUnit tests. The {@link NotificationTestHelper} will take care for thread handling.
*
* <p>
* Example:
* <p>
*
* <pre>
*
* public class MyViewModel implements ViewModel {
* public static final String ACTION_KEY = "my-action";
*
* public void someAction() {
* ...
* publish(ACTION_KEY);
* }
* }
*
* // unit test
* {@code @Test}
* public void testSomething() {
* MyViewModel viewModel = new MyViewModel();
*
* NotificationTestHelper helper = new NotificationTestHelper();
* viewModel.subscribe(MyViewModel.ACTION_KEY, helper);
*
*
* viewModel.someAction();
*
* assertEquals(1, helper.numberOfReceivedNotifications());
* }
* </pre>
*
*
*
* You can provide a timeout as constructor parameter.
* This is useful in case of asynchronous code (f.e. when notifications are send from another Thread).
*
* By default the timeout is set to {@value #DEFAULT_TIMEOUT}. When you have a long running thread
* you should use a higher timeout.
*
* @author manuel.mauky
*/
public class NotificationTestHelper implements NotificationObserver {

public static final long DEFAULT_TIMEOUT = 0l;

private List<Pair<String, Object[]>> notifications = new ArrayList<>();

private long timeout = DEFAULT_TIMEOUT;

/**
* Create a test helper with a default timeout of {@value #DEFAULT_TIMEOUT} millis.
*/
public NotificationTestHelper() {
new JFXPanel();
}

/**
* Create a test helper with the given timeout in millis.
*
* @param timeoutInMillis the timeout.
*/
public NotificationTestHelper(long timeoutInMillis) {
this();
this.timeout = timeoutInMillis;
}

@Override
public void receivedNotification(String key, Object... payload) {
notifications.add(new Pair<>(key, payload));
}

/**
* @return the number of received notifications.
*/
public int numberOfReceivedNotifications() {
waitForUiThread();
return notifications.size();
}

/**
* @param key the key of the notification.
* @return the number of received notifications for the given key.
*/
public int numberOfReceivedNotifications(String key) {
waitForUiThread();
return (int) notifications.stream()
.filter(pair -> pair.getKey().equals(key))
.count();
}

private void waitForUiThread() {
CompletableFuture<Void> future = new CompletableFuture<>();

Platform.runLater(() -> {
if(timeout > 0) {
try {
Thread.sleep(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
future.complete(null);
});

try {
future.get(timeout+50, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package de.saxsys.mvvmfx.utils.notifications;

import static org.assertj.core.api.Assertions.*;

import org.junit.Before;
import org.junit.Test;

import de.saxsys.mvvmfx.ViewModel;

/**
* @author manuel.mauky
*/
public class NotificationTestHelperTest {

public class MyViewModel implements ViewModel {
}

private MyViewModel viewModel;

@Before
public void setUp() throws Exception {
viewModel = new MyViewModel();
}
@Test
public void singlePublish () {
NotificationTestHelper helper = new NotificationTestHelper();
viewModel.subscribe("test", helper);

viewModel.publish("test");

assertThat(helper.numberOfReceivedNotifications()).isEqualTo(1);
}

@Test
public void multiplePublish() {
NotificationTestHelper helper = new NotificationTestHelper();
viewModel.subscribe("test", helper);

int n = 10;

for(int i=0 ; i<n; i++) {
viewModel.publish("test");
}

assertThat(helper.numberOfReceivedNotifications()).isEqualTo(n);
}

@Test
public void globalNotificationCenter() {
NotificationTestHelper helper = new NotificationTestHelper();

NotificationCenter notificationCenter = new DefaultNotificationCenter();

notificationCenter.subscribe("OK", helper);


notificationCenter.publish("OK");


assertThat(helper.numberOfReceivedNotifications()).isEqualTo(1);
}

@Test
public void publishOnOtherThread(){
NotificationTestHelper helper = new NotificationTestHelper(50l);

NotificationCenter notificationCenter = new DefaultNotificationCenter();

notificationCenter.subscribe("OK", helper);


Runnable r = () -> notificationCenter.publish("OK");

new Thread(r).start();

assertThat(helper.numberOfReceivedNotifications()).isEqualTo(1);
}

@Test
public void timeout() {
NotificationTestHelper helper = new NotificationTestHelper(150l);

NotificationCenter notificationCenter = new DefaultNotificationCenter();

notificationCenter.subscribe("OK", helper);


Runnable r = () -> {
try {
Thread.sleep(100l);
} catch (InterruptedException e) {
e.printStackTrace();
}
notificationCenter.publish("OK");
};

new Thread(r).start();

assertThat(helper.numberOfReceivedNotifications()).isEqualTo(1);
}
}

0 comments on commit 7290ee9

Please sign in to comment.