Repository: assertj/assertj (Java)

Issue:
Assertion hasNoNullFieldsOrProperties() throw error for delegate Map implementation
#### Summary

We have some own classes with extra logic that implement Map interface but delegate invocation to backed HashMap. This is related commit https://github.com/joel-costigliola/assertj-core/commit/337f99eee5cdcab9270145c47b429cbc48fb0134

#### Example

```java
 @Test
    void expectedHasNoNullFieldsOrProperties() {
        DelegateMap map = new DelegateMap(new HashMap<>());
        Assertions.assertThat(map).hasNoNullFieldsOrProperties();
    }

    static class DelegateMap implements Map<String, String> {

        private final Map<String, String> delegate;

        public DelegateMap(Map<String, String> delegate) {
            this.delegate = delegate;
        }

        @Override
        public int size() {
            return delegate.size();
        }

        @Override
        public boolean isEmpty() {
            return delegate.isEmpty();
        }

        @Override
        public boolean containsKey(Object key) {
            return delegate.containsKey(key);
        }

        @Override
        public boolean containsValue(Object value) {
            return delegate.containsValue(value);
        }

        @Override
        public String get(Object key) {
            return delegate.get(key);
        }

        @Override
        public String put(String key, String value) {
            return delegate.put(key, value);
        }

        @Override
        public String remove(Object key) {
            return delegate.remove(key);
        }

        @Override
        public void putAll(Map<? extends String, ? extends String> m) {
            delegate.putAll(m);
        }

        @Override
        public void clear() {
            delegate.clear();
        }

        @Override
        public Set<String> keySet() {
            return delegate.keySet();
        }

        @Override
        public Collection<String> values() {
            return delegate.values();
        }

        @Override
        public Set<Entry<String, String>> entrySet() {
            return delegate.entrySet();
        }

        @Override
        public boolean equals(Object o) {
            return delegate.equals(o);
        }

        @Override
        public int hashCode() {
            return delegate.hashCode();
        }

        @Override
        public String getOrDefault(Object key, String defaultValue) {
            return delegate.getOrDefault(key, defaultValue);
        }

        @Override
        public void forEach(BiConsumer<? super String, ? super String> action) {
            delegate.forEach(action);
        }

        @Override
        public void replaceAll(BiFunction<? super String, ? super String, ? extends String> function) {
            delegate.replaceAll(function);
        }

        @Override
        public String putIfAbsent(String key, String value) {
            return delegate.putIfAbsent(key, value);
        }

        @Override
        public boolean remove(Object key, Object value) {
            return delegate.remove(key, value);
        }

        @Override
        public boolean replace(String key, String oldValue, String newValue) {
            return delegate.replace(key, oldValue, newValue);
        }

        @Override
        public String replace(String key, String value) {
            return delegate.replace(key, value);
        }

        @Override
        public String computeIfAbsent(String key, Function<? super String, ? extends String> mappingFunction) {
            return delegate.computeIfAbsent(key, mappingFunction);
        }

        @Override
        public String computeIfPresent(String key, BiFunction<? super String, ? super String, ? extends String> remappingFunction) {
            return delegate.computeIfPresent(key, remappingFunction);
        }

        @Override
        public String compute(String key, BiFunction<? super String, ? super String, ? extends String> remappingFunction) {
            return delegate.compute(key, remappingFunction);
        }

        @Override
        public String merge(String key, String value, BiFunction<? super String, ? super String, ? extends String> remappingFunction) {
            return delegate.merge(key, value, remappingFunction);
        }
    }
```



Hints:
Thanks for reporting this, we are going to look at it shortly!

Patch:
diff --git a/src/main/java/org/assertj/core/util/introspection/PropertyOrFieldSupport.java b/src/main/java/org/assertj/core/util/introspection/PropertyOrFieldSupport.java
--- a/src/main/java/org/assertj/core/util/introspection/PropertyOrFieldSupport.java
+++ b/src/main/java/org/assertj/core/util/introspection/PropertyOrFieldSupport.java
@@ -15,10 +15,10 @@
 import static java.lang.String.format;
 import static org.assertj.core.util.Preconditions.checkArgument;
 
-import org.assertj.core.util.VisibleForTesting;
-
 import java.util.Map;
 
+import org.assertj.core.util.VisibleForTesting;
+
 public class PropertyOrFieldSupport {
   private static final String SEPARATOR = ".";
   private PropertySupport propertySupport;
@@ -60,28 +60,30 @@ public Object getValueOf(String propertyOrFieldName, Object input) {
     return getSimpleValue(propertyOrFieldName, input);
   }
 
-  public Object getSimpleValue(String propertyOrFieldName, Object input) {
-    // first check if input object is a map
-    if (input instanceof Map) {
-      Map<?, ?> map = (Map<?, ?>) input;
-      return map.get(propertyOrFieldName);
-    }
-
-    // then try to get given property values from objects, then try fields
+  public Object getSimpleValue(String name, Object input) {
+    // try to get name as a property, then try as a field, then try as a map key
     try {
-      return propertySupport.propertyValueOf(propertyOrFieldName, Object.class, input);
+      return propertySupport.propertyValueOf(name, Object.class, input);
     } catch (IntrospectionError propertyIntrospectionError) {
-      // no luck with properties, let's try fields
+      // no luck as a property, let's try as a field
       try {
-        return fieldSupport.fieldValue(propertyOrFieldName, Object.class, input);
+        return fieldSupport.fieldValue(name, Object.class, input);
       } catch (IntrospectionError fieldIntrospectionError) {
-        // no field nor property found with given name, it is considered as an error
+        // neither field nor property found with given name
+
+        // if the input object is a map, try name as a map key
+        if (input instanceof Map) {
+          Map<?, ?> map = (Map<?, ?>) input;
+          return map.get(name);
+        }
+
+        // no value found with given name, it is considered as an error
         String message = format("%nCan't find any field or property with name '%s'.%n" +
                                 "Error when introspecting properties was :%n" +
                                 "- %s %n" +
                                 "Error when introspecting fields was :%n" +
                                 "- %s",
-                                propertyOrFieldName, propertyIntrospectionError.getMessage(),
+                                name, propertyIntrospectionError.getMessage(),
                                 fieldIntrospectionError.getMessage());
         throw new IntrospectionError(message, fieldIntrospectionError);
       }


Test patch:
diff --git a/src/test/java/org/assertj/core/util/introspection/PropertyOrFieldSupport_getSimpleValue_with_Map_input_Test.java b/src/test/java/org/assertj/core/util/introspection/PropertyOrFieldSupport_getSimpleValue_with_Map_input_Test.java
new file mode 100644
--- /dev/null
+++ b/src/test/java/org/assertj/core/util/introspection/PropertyOrFieldSupport_getSimpleValue_with_Map_input_Test.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * Copyright 2012-2020 the original author or authors.
+ */
+package org.assertj.core.util.introspection;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.HashMap;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for <code>{@link PropertyOrFieldSupport#getSimpleValue(String, Object)}</code> with {@code Map} input.
+  *
+  * @author Stefano Cordio
+  */
+@DisplayName("PropertyOrFieldSupport getSimpleValue(String, Map)")
+class PropertyOrFieldSupport_getSimpleValue_with_Map_input_Test {
+
+  private final PropertyOrFieldSupport underTest = PropertyOrFieldSupport.EXTRACTION;
+  private final AbstractMap<String, String> map = new HashMap<>();
+
+  @Test
+  void should_extract_property_value_even_if_map_key_matches_given_name() {
+    // GIVEN
+    map.put("empty", "value"); // key clashes with AbstractMap#isEmpty()
+    // WHEN
+    Object value = underTest.getSimpleValue("empty", map);
+    // THEN
+    then(value).isInstanceOf(Boolean.class);
+  }
+
+  @Test
+  void should_extract_field_value_even_if_map_key_matches_given_name() {
+    // GIVEN
+    map.put("keySet", "value"); // key clashes with AbstractMap#keySet
+    // WHEN
+    Object value = underTest.getSimpleValue("keySet", map);
+    // THEN
+    then(value).isInstanceOf(Collection.class);
+  }
+
+  @Test
+  void should_extract_map_value_when_no_property_or_field_matches_given_name() {
+    // GIVEN
+    map.put("key", "value");
+    // WHEN
+    Object value = underTest.getSimpleValue("key", map);
+    // THEN
+    then(value).isEqualTo("value");
+  }
+
+  @Test
+  void should_extract_null_when_given_name_is_not_found() {
+    // WHEN
+    Object value = underTest.getSimpleValue("unknown", map);
+    // THEN
+    then(value).isNull();
+  }
+
+}