/* * Copyright 2015-2017 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution and is available at * * http://www.eclipse.org/legal/epl-v10.html */ package org.junit.jupiter.engine.execution; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.function.Function; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ExtensionContextException; /** * Microtests for {@link ExtensionValuesStore}. * * @since 5.0 * @see ExtensionContextTests */ class ExtensionValuesStoreTests { private final Object key = "key"; private final Object value = "value"; private final Namespace namespace = Namespace.create("ns"); private ExtensionValuesStore store; private ExtensionValuesStore parentStore; @BeforeEach void initializeStore() { parentStore = new ExtensionValuesStore(); store = new ExtensionValuesStore(parentStore); } @Nested class StoringValuesTests { @Test void getWithUnknownKeyReturnsNull() { assertNull(store.get(namespace, "unknown key")); } @Test void putAndGetWithSameKey() { store.put(namespace, key, value); assertEquals(value, store.get(namespace, key)); } @Test void valueCanBeReplaced() { store.put(namespace, key, value); Object newValue = new Object(); store.put(namespace, key, newValue); assertEquals(newValue, store.get(namespace, key)); } @Test void valueIsComputedIfAbsent() { assertNull(store.get(namespace, key)); assertEquals(value, store.getOrComputeIfAbsent(namespace, key, innerKey -> value)); assertEquals(value, store.get(namespace, key)); } @Test void valueIsNotComputedIfPresentLocally() { store.put(namespace, key, value); assertEquals(value, store.getOrComputeIfAbsent(namespace, key, innerKey -> "a different value")); assertEquals(value, store.get(namespace, key)); } @Test void valueIsNotComputedIfPresentInParent() { parentStore.put(namespace, key, value); assertEquals(value, store.getOrComputeIfAbsent(namespace, key, k -> "a different value")); assertEquals(value, store.get(namespace, key)); } @Test void nullIsAValidValueToPut() { store.put(namespace, key, null); assertNull(store.getOrComputeIfAbsent(namespace, key, innerKey -> "a different value")); assertNull(store.get(namespace, key)); } @Test void keysCanBeRemoved() { store.put(namespace, key, value); assertEquals(value, store.remove(namespace, key)); assertNull(store.get(namespace, key)); assertEquals("a different value", store.getOrComputeIfAbsent(namespace, key, innerKey -> "a different value")); } @Test void sameKeyWithDifferentNamespaces() { Object value1 = createObject("value1"); Namespace namespace1 = Namespace.create("ns1"); Object value2 = createObject("value2"); Namespace namespace2 = Namespace.create("ns2"); store.put(namespace1, key, value1); store.put(namespace2, key, value2); assertEquals(value1, store.get(namespace1, key)); assertEquals(value2, store.get(namespace2, key)); } @Test void valueIsComputedIfAbsentInDifferentNamespace() { Namespace namespace1 = Namespace.create("ns1"); Namespace namespace2 = Namespace.create("ns2"); assertEquals(value, store.getOrComputeIfAbsent(namespace1, key, innerKey -> value)); assertEquals(value, store.get(namespace1, key)); assertNull(store.get(namespace2, key)); } @Test void keyIsOnlyRemovedInGivenNamespace() { Namespace namespace1 = Namespace.create("ns1"); Namespace namespace2 = Namespace.create("ns2"); Object value1 = createObject("value1"); Object value2 = createObject("value2"); store.put(namespace1, key, value1); store.put(namespace2, key, value2); store.remove(namespace1, key); assertNull(store.get(namespace1, key)); assertEquals(value2, store.get(namespace2, key)); } @Test void getWithTypeSafetyAndInvalidRequiredTypeThrowsException() { Integer key = 42; String value = "enigma"; store.put(namespace, key, value); Exception exception = assertThrows(ExtensionContextException.class, () -> store.get(namespace, key, Number.class)); assertEquals("Object stored under key [42] is not of required type [java.lang.Number]", exception.getMessage()); } @Test void getWithTypeSafety() { Integer key = 42; String value = "enigma"; store.put(namespace, key, value); // The fact that we can declare this as a String suffices for testing the required type. String requiredTypeValue = store.get(namespace, key, String.class); assertEquals(value, requiredTypeValue); } @Test void getWithTypeSafetyAndPrimitiveValueType() { String key = "enigma"; int value = 42; store.put(namespace, key, value); // The fact that we can declare this as an int/Integer suffices for testing the required type. int requiredInt = store.get(namespace, key, int.class); Integer requiredInteger = store.get(namespace, key, Integer.class); assertEquals(value, requiredInt); assertEquals(value, requiredInteger.intValue()); } @Test void getNullValueWithTypeSafety() { store.put(namespace, key, null); // The fact that we can declare this as a String suffices for testing the required type. String requiredTypeValue = store.get(namespace, key, String.class); assertNull(requiredTypeValue); } @Test void getOrComputeIfAbsentWithTypeSafetyAndInvalidRequiredTypeThrowsException() { String key = "pi"; Float value = Float.valueOf(3.14f); // Store a Float... store.put(namespace, key, value); // But declare that our function creates a String... Function<String, String> defaultCreator = k -> "enigma"; Exception exception = assertThrows(ExtensionContextException.class, () -> store.getOrComputeIfAbsent(namespace, key, defaultCreator, String.class)); assertEquals("Object stored under key [pi] is not of required type [java.lang.String]", exception.getMessage()); } @Test void getOrComputeIfAbsentWithTypeSafety() { Integer key = 42; String value = "enigma"; // The fact that we can declare this as a String suffices for testing the required type. String computedValue = store.getOrComputeIfAbsent(namespace, key, k -> value, String.class); assertEquals(value, computedValue); } @Test void getOrComputeIfAbsentWithTypeSafetyAndPrimitiveValueType() { String key = "enigma"; int value = 42; // The fact that we can declare this as an int/Integer suffices for testing the required type. int computedInt = store.getOrComputeIfAbsent(namespace, key, k -> value, int.class); Integer computedInteger = store.getOrComputeIfAbsent(namespace, key, k -> value, Integer.class); assertEquals(value, computedInt); assertEquals(value, computedInteger.intValue()); } @Test void removeWithTypeSafetyAndInvalidRequiredTypeThrowsException() { Integer key = 42; String value = "enigma"; store.put(namespace, key, value); Exception exception = assertThrows(ExtensionContextException.class, () -> store.remove(namespace, key, Number.class)); assertEquals("Object stored under key [42] is not of required type [java.lang.Number]", exception.getMessage()); } @Test void removeWithTypeSafety() { Integer key = 42; String value = "enigma"; store.put(namespace, key, value); // The fact that we can declare this as a String suffices for testing the required type. String removedValue = store.remove(namespace, key, String.class); assertEquals(value, removedValue); assertNull(store.get(namespace, key)); } @Test void removeWithTypeSafetyAndPrimitiveValueType() { String key = "enigma"; int value = 42; store.put(namespace, key, value); // The fact that we can declare this as an int suffices for testing the required type. int requiredInt = store.remove(namespace, key, int.class); assertEquals(value, requiredInt); store.put(namespace, key, value); // The fact that we can declare this as an Integer suffices for testing the required type. Integer requiredInteger = store.get(namespace, key, Integer.class); assertEquals(value, requiredInteger.intValue()); } @Test void removeNullValueWithTypeSafety() { Integer key = 42; store.put(namespace, key, null); // The fact that we can declare this as a String suffices for testing the required type. String removedValue = store.remove(namespace, key, String.class); assertNull(removedValue); assertNull(store.get(namespace, key)); } } @Nested class InheritedValuesTests { @Test void valueFromParentIsVisible() { parentStore.put(namespace, key, value); assertEquals(value, store.get(namespace, key)); } @Test void valueFromParentCanBeOverriddenInChild() { parentStore.put(namespace, key, value); Object otherValue = new Object(); store.put(namespace, key, otherValue); assertEquals(otherValue, store.get(namespace, key)); assertEquals(value, parentStore.get(namespace, key)); } } @Nested class CompositeNamespaceTests { @Test void namespacesEqualForSamePartsSequence() { Namespace ns1 = Namespace.create("part1", "part2"); Namespace ns2 = Namespace.create("part1", "part2"); assertEquals(ns1, ns2); } @Test void orderOfNamespacePartsDoesMatter() { Namespace ns1 = Namespace.create("part1", "part2"); Namespace ns2 = Namespace.create("part2", "part1"); assertNotEquals(ns1, ns2); } @Test void additionNamespacePartMakesADifferenc() { Namespace ns1 = Namespace.create("part1", "part2"); Namespace ns2 = Namespace.create("part1"); Namespace ns3 = Namespace.create("part1", "part2"); Object value2 = createObject("value2"); parentStore.put(ns1, key, value); parentStore.put(ns2, key, value2); assertEquals(value, store.get(ns1, key)); assertEquals(value, store.get(ns3, key)); assertEquals(value2, store.get(ns2, key)); } } private Object createObject(final String display) { return new Object() { @Override public String toString() { return display; } }; } }