소스 검색

Start adding test cases for Observer code.

Sam Jaffe 5 년 전
부모
커밋
ad38359f22

+ 21 - 6
pom.xml

@@ -80,12 +80,6 @@
   </build>
 
   <dependencies>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <version>3.8.1</version>
-      <scope>test</scope>
-    </dependency>
     <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
@@ -108,5 +102,26 @@
       <version>0.1</version>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-engine</artifactId>
+      <version>5.7.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-api</artifactId>
+      <version>5.7.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>3.6.28</version>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-junit-jupiter</artifactId>
+      <version>3.6.28</version>
+    </dependency>
   </dependencies>
 </project>

+ 5 - 3
src/main/lombok/org/leumasjaffe/observer/ObservableListener.java

@@ -1,5 +1,6 @@
 package org.leumasjaffe.observer;
 
+import java.util.Objects;
 import java.util.function.BiConsumer;
 
 import lombok.AccessLevel;
@@ -26,18 +27,19 @@ public class ObservableListener<C, T extends Observable> {
 	 * {@see IndirectObservableListener#setObserved}
 	 */
 	public void setObserved( T obs ) {
+		Objects.requireNonNull(obs);
 		if (impl.getModel() == obs) { return; }
 		impl.setObserved(obs, obs);
 	}
 	
-	public void notifySubscribers(Observable obs) {
-		this.impl.notifySubscribers(obs);
+	public void notifySubscribers() {
+		this.impl.notifySubscribers(this.impl.getModel());
 	}
 	
 	/**
 	 * Create a Listener where the component being 'updated' is another Observable object.
 	 * The behavior of this listener is to cascade a notice between two objects that are
-	 * related in a context, but not in the datamodel.
+	 * related in a context, but not in the data model.
 	 */
 	public static <C extends Observable, T extends Observable> ObservableListener<C, T> cascade(T from, C to) {
 		ObservableListener<C, T> lis = new ObservableListener<C, T>(to, ObservableListener::cascadeImpl);

+ 44 - 0
src/test/java/org/leumasjaffe/observer/ForwardingObservableListenerTest.java

@@ -0,0 +1,44 @@
+package org.leumasjaffe.observer;
+
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class ForwardingObservableListenerTest {
+	
+	@Spy MockObserverListener listener;
+	Observable.Instance target = new Observable.Instance();
+	
+	@BeforeEach
+	void setUp() {
+		listener.setObserved(target);
+		clearInvocations(listener);
+	}
+
+	@Test
+	void testUpdatesParentWhenObserving() {
+		ForwardingObservableListener<Observable> forwarder = new ForwardingObservableListener<>();
+		Observable.Instance child = new Observable.Instance();
+
+		forwarder.setObserved(target, child);
+		
+		verify(listener).updateWasSignalled();
+	}
+
+	@Test
+	void testForwardsMessageFromObjectToObject() {
+		ForwardingObservableListener<Observable> forwarder = new ForwardingObservableListener<>();
+		Observable.Instance child = new Observable.Instance();
+
+		forwarder.setObserved(target, child);
+		ObserverDispatch.notifySubscribers(child);
+
+		verify(listener, times(2)).updateWasSignalled();
+	}
+
+}

+ 144 - 0
src/test/java/org/leumasjaffe/observer/IndirectObservableListenerTest.java

@@ -0,0 +1,144 @@
+package org.leumasjaffe.observer;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class IndirectObservableListenerTest {
+	
+	@Mock MockObserverUpdate update;
+	IndirectObservableListener<Void, Object> listener;
+
+	@BeforeEach
+	void setUp() {
+		listener = new IndirectObservableListener<>(null, update);
+	}
+	
+	@Test
+	void testCannotObserveNull() {
+		assertThrows(NullPointerException.class,
+				() -> listener.setObserved(null));
+	}
+
+	@Test
+	void testCanObserveZeroObjects() {
+		assertDoesNotThrow(() -> listener.setObserved(new Object()));
+	}
+
+	@Test
+	void testCanObserveMultipleObjects() {
+		final Observable.Instance[] objects = {
+				new Observable.Instance(),
+				new Observable.Instance()
+		};
+		assertDoesNotThrow(() -> listener.setObserved(new Object(), objects));
+	}
+
+	@Test
+	void testCanObserveCollectionOfObjects() {
+		final List<Observable.Instance> objects = Arrays.asList(
+				new Observable.Instance(),
+				new Observable.Instance()
+		);
+		assertDoesNotThrow(() -> listener.setObserved(new Object(), objects));
+	}
+
+	@Test
+	void testSettingObservationTriggersUpdate() {
+		final Object obj = new Object();
+		listener.setObserved(obj);
+		
+		verify(update).accept(isNull(), same(obj));
+	}
+
+	@Test
+	void testChangingObservationTriggersUpdate() {
+		final Object obj = new Object();
+		final Object next = new Object();
+		listener.setObserved(obj);
+		listener.setObserved(next);
+		
+		verify(update).accept(isNull(), same(next));
+	}
+
+	@Test
+	void testReApplyingObservationDoesNotUpdate() {
+		final Object obj = new Object();
+		listener.setObserved(obj);
+		listener.setObserved(obj);
+		
+		verify(update).accept(isNull(), same(obj));
+	}
+
+	@Test
+	void testNotifyFromObservedObjectWillTriggerUpdate() {
+		final Observable.Instance[] objects = {
+				new Observable.Instance(),
+				new Observable.Instance()
+		};
+		final Object obj = new Object();
+		listener.setObserved(obj, objects);
+		
+		ObserverDispatch.notifySubscribers(objects[0]);
+		ObserverDispatch.notifySubscribers(objects[1]);
+		verify(update, times(3)).accept(isNull(), same(obj));
+	}
+
+	@Test
+	void testOverwritesNotifyListOnUpdate() {
+		final Observable.Instance[] objects = {
+				new Observable.Instance(),
+				new Observable.Instance()
+		};
+		final Object obj = new Object();
+		listener.setObserved(obj, objects);
+		listener.setObserved(obj);
+
+		verify(update).accept(isNull(), same(obj));
+		ObserverDispatch.notifySubscribers(objects[0]);
+		ObserverDispatch.notifySubscribers(objects[1]);
+		verify(update).accept(isNull(), same(obj));
+	}
+	
+	@Test
+	void testDeployingNotifyFromListenerDoesNotUpdate() {
+		final Observable.Instance[] objects = {
+				new Observable.Instance(),
+				new Observable.Instance()
+		};
+		final Object obj = new Object();
+		listener.setObserved(obj, objects);
+		
+		verify(update).accept(isNull(), same(obj));
+		listener.notifySubscribers(objects[0]);
+		verify(update).accept(isNull(), same(obj));
+	}
+	
+	@Test
+	void testCannotRecursivelyUpdate() {
+		final Observable.Instance[] objects = {
+				new Observable.Instance(),
+				new Observable.Instance()
+		};
+		final Object obj = new Object();
+		listener.setObserved(obj, objects);
+		
+		doAnswer(inv -> {
+				ObserverDispatch.notifySubscribers(objects[0]);
+				return null;
+		}).when(update).accept(any(), any());
+
+		verify(update).accept(isNull(), same(obj));
+		ObserverDispatch.notifySubscribers(objects[0]);
+		verify(update, times(2)).accept(isNull(), same(obj));
+	}
+}

+ 12 - 0
src/test/java/org/leumasjaffe/observer/MockObserverListener.java

@@ -0,0 +1,12 @@
+package org.leumasjaffe.observer;
+
+public abstract class MockObserverListener {
+	private final ObservableListener<Void, Observable> impl =
+			new ObservableListener<>(null, (v, i) -> updateWasSignalled());
+	
+	public final void setObserved(Observable object) {
+		impl.setObserved(object);
+	}
+
+	public abstract void updateWasSignalled();
+}

+ 7 - 0
src/test/java/org/leumasjaffe/observer/MockObserverUpdate.java

@@ -0,0 +1,7 @@
+package org.leumasjaffe.observer;
+
+import java.util.function.BiConsumer;
+
+interface MockObserverUpdate extends BiConsumer<Void, Object> {
+
+}

+ 104 - 0
src/test/java/org/leumasjaffe/observer/ObservableListenerTest.java

@@ -0,0 +1,104 @@
+package org.leumasjaffe.observer;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class ObservableListenerTest {
+	
+	@Mock MockObserverUpdate update;
+	ObservableListener<Void, Observable> listener;
+
+	@BeforeEach
+	void setUp() {
+		listener = new ObservableListener<>(null, update);
+	}
+	
+	@Test
+	void testCannotObserveNull() {
+		assertThrows(NullPointerException.class,
+				() -> listener.setObserved(null));
+	}
+
+	@Test
+	void testCanObserveObject() {
+		assertDoesNotThrow(() -> listener.setObserved(new Observable.Instance()));
+	}
+
+	@Test
+	void testSettingObservationTriggersUpdate() {
+		final Observable.Instance obj = new Observable.Instance();
+		listener.setObserved(obj);
+		
+		verify(update).accept(isNull(), same(obj));
+	}
+
+	@Test
+	void testChangingObservationTriggersUpdate() {
+		final Observable.Instance obj = new Observable.Instance();
+		final Observable.Instance next = new Observable.Instance();
+		listener.setObserved(obj);
+		listener.setObserved(next);
+		
+		verify(update).accept(isNull(), same(next));
+	}
+
+	@Test
+	void testReApplyingObservationDoesNotUpdate() {
+		final Observable.Instance obj = new Observable.Instance();
+		listener.setObserved(obj);
+		listener.setObserved(obj);
+		
+		verify(update).accept(isNull(), same(obj));
+	}
+
+	@Test
+	void testNotifyFromObservedObjectWillTriggerUpdate() {
+		final Observable.Instance obj = new Observable.Instance();
+		listener.setObserved(obj);
+		
+		ObserverDispatch.notifySubscribers(obj);
+		verify(update, times(2)).accept(isNull(), same(obj));
+	}
+	
+	@Test
+	void testDeployingNotifyFromListenerDoesNotUpdate() {
+		final Observable.Instance obj = new Observable.Instance();
+		listener.setObserved(obj);
+		
+		verify(update).accept(isNull(), same(obj));
+		listener.notifySubscribers();
+		verify(update).accept(isNull(), same(obj));
+	}
+	
+	@Test
+	void testCannotRecursivelyUpdate() {
+		final Observable.Instance obj = new Observable.Instance();
+		listener.setObserved(obj);
+		
+		doAnswer(inv -> {
+				ObserverDispatch.notifySubscribers(obj);
+				return null;
+		}).when(update).accept(any(), any());
+
+		verify(update).accept(isNull(), same(obj));
+		ObserverDispatch.notifySubscribers(obj);
+		verify(update, times(2)).accept(isNull(), same(obj));
+	}
+	
+	@Test
+	void testCanCascadeNotifications() {
+		final Observable.Instance obj = new Observable.Instance();
+		final Observable.Instance child = new Observable.Instance();
+		listener.setObserved(obj);
+		
+		ObservableListener.cascade(child, obj).notifySubscribers();
+		verify(update, times(2)).accept(isNull(), same(obj));
+	}
+}