Repository: assertj/assertj

Problem statement:
Compile error due to new assertThatThrownBy
**Describe the bug**
Compile error in Eclipse 2023-12: The method assertThatThrownBy(ThrowableAssert.ThrowingCallable) is ambiguous for the type AmbigiousCallTest.

The only change is the upgrade from 3.24.2 to 3.25. In 3.24.2 the same code was fine.

* assertj core version: 3.25

**Test case reproducing the bug**

Don't *run* the below code, but just *compile* it. At least ECJ doesn't like it.

```java
public class AmbigiousCallTest {

    @Test
    void ambigiousCall() throws Exception {
        assertThatThrownBy(AmbigiousCallTest::verify)
                .hasMessageContaining("No objects are given");
    }

    public static void verify(final Object... objects) {
        // foo
    }

}
```

Probably related to #3043. Workaround: Extract the method reference to a local variable. The type information of that local variable is sufficient to choose one of the two methods.


Hints:
Thanks for reporting it, @Bananeweizen.

We're in discussion with the team if we should revert cf06398587da23ac530b03a941e0efe6cf29aef7 as it introduced a breaking change we didn't spot before.
My code also doesn't compile after upgrading to AssertJ 3.25.0,
BUT

@scordio I would like to mention that this is not a big deal for me. 
I just need to replace method reference (which is not really convenient to read):
```java
assertThatThrownBy(Selenide::confirm)
      .isInstanceOf(AlertNotFoundError.class)
```

to a "standard" lambda (which is better readable to me):
```java
assertThatThrownBy(() -> Selenide.confirm())
      .isInstanceOf(AlertNotFoundError.class)
```
We got hit by this in Spring Framework as well.

https://github.com/spring-projects/spring-framework/commit/ffddbb586e60ce1b66a3b962612636db9c61ed81#diff-58b8a6574dcffc60614d0727c28b36039f5bc28a04143486722d908a24ff55c7
FWIW, if you do intend to keep `ThrowingCallableWithValue`, note that most people typically call that a `ThrowingSupplier`. 😉 
**Team decision**: cf06398587da23ac530b03a941e0efe6cf29aef7 should be reverted and #3043 should be evaluated again together with #1652 / #2519.

Patch:
diff --git a/assertj-core/src/main/java/org/assertj/core/api/Assertions.java b/assertj-core/src/main/java/org/assertj/core/api/Assertions.java
--- a/assertj-core/src/main/java/org/assertj/core/api/Assertions.java
+++ b/assertj-core/src/main/java/org/assertj/core/api/Assertions.java
@@ -12,7 +12,6 @@
  */
 package org.assertj.core.api;
 
-import static java.lang.String.format;
 import static org.assertj.core.configuration.ConfigurationProvider.CONFIGURATION_PROVIDER;
 import static org.assertj.core.data.Percentage.withPercentage;
 
@@ -76,7 +75,6 @@
 import java.util.stream.Stream;
 
 import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
-import org.assertj.core.api.ThrowableAssert.ThrowingCallableWithValue;
 import org.assertj.core.api.filter.FilterOperator;
 import org.assertj.core.api.filter.Filters;
 import org.assertj.core.api.filter.InFilter;
@@ -98,7 +96,6 @@
 import org.assertj.core.description.Description;
 import org.assertj.core.groups.Properties;
 import org.assertj.core.groups.Tuple;
-import org.assertj.core.internal.Failures;
 import org.assertj.core.presentation.BinaryRepresentation;
 import org.assertj.core.presentation.HexadecimalRepresentation;
 import org.assertj.core.presentation.Representation;
@@ -1212,26 +1209,6 @@ public static <T extends Throwable> AbstractThrowableAssert<?, T> assertThat(T a
     return assertThat(catchThrowable(shouldRaiseThrowable)).hasBeenThrown();
   }
 
-  /**
-   * Similar to {@link #assertThatThrownBy(ThrowingCallable)}, but when the called code returns a value instead of
-   * throwing, the assertion error shows the returned value to help understand what went wrong.
-   *
-   * @param shouldRaiseThrowable The {@link ThrowingCallableWithValue} or lambda with the code that should raise the throwable.
-   * @return the created {@link ThrowableAssert}.
-   * @since 3.25.0
-   */
-  @CanIgnoreReturnValue
-  public static AbstractThrowableAssert<?, ? extends Throwable> assertThatThrownBy(
-                                                                                   ThrowingCallableWithValue shouldRaiseThrowable) {
-    Object value;
-    try {
-      value = shouldRaiseThrowable.call();
-    } catch (Throwable throwable) {
-      return assertThat(throwable);
-    }
-    throw Failures.instance().failure(format("Expecting code to raise a throwable, but it returned [%s] instead", value));
-  }
-
   /**
    * Allows to capture and then assert on a {@link Throwable} like {@code assertThatThrownBy(ThrowingCallable)} but this method
    * let you set the assertion description the same way you do with {@link AbstractAssert#as(String, Object...) as(String, Object...)}.
@@ -1270,26 +1247,6 @@ public static <T extends Throwable> AbstractThrowableAssert<?, T> assertThat(T a
     return assertThat(catchThrowable(shouldRaiseThrowable)).as(description, args).hasBeenThrown();
   }
 
-  /**
-   * Similar to {@link #assertThatThrownBy(ThrowingCallable, String, Object...)}, but when the called code returns a value
-   * instead of throwing, the assertion error shows the returned value to help understand what went wrong.
-   *
-   * @param shouldRaiseThrowable The {@link ThrowingCallableWithValue} or lambda with the code that should raise the throwable.
-   * @return the created {@link ThrowableAssert}.
-   * @since 3.25.0
-   */
-  @CanIgnoreReturnValue
-  public static AbstractThrowableAssert<?, ? extends Throwable> assertThatThrownBy(ThrowingCallableWithValue shouldRaiseThrowable,
-                                                                                   String description, Object... args) {
-    Object value;
-    try {
-      value = shouldRaiseThrowable.call();
-    } catch (Throwable throwable) {
-      return assertThat(throwable).as(description, args);
-    }
-    throw Failures.instance().failure(format("Expecting code to raise a throwable, but it returned [%s] instead", value));
-  }
-
   /**
    * Allows to capture and then assert on a {@link Throwable} (easier done with lambdas).
    * <p>
diff --git a/assertj-core/src/main/java/org/assertj/core/api/BDDAssertions.java b/assertj-core/src/main/java/org/assertj/core/api/BDDAssertions.java
--- a/assertj-core/src/main/java/org/assertj/core/api/BDDAssertions.java
+++ b/assertj-core/src/main/java/org/assertj/core/api/BDDAssertions.java
@@ -12,8 +12,6 @@
  */
 package org.assertj.core.api;
 
-import static java.lang.String.format;
-
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -74,7 +72,6 @@
 import java.util.stream.Stream;
 
 import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
-import org.assertj.core.api.ThrowableAssert.ThrowingCallableWithValue;
 import org.assertj.core.api.filter.FilterOperator;
 import org.assertj.core.api.filter.InFilter;
 import org.assertj.core.api.filter.NotFilter;
@@ -92,7 +89,6 @@
 import org.assertj.core.description.Description;
 import org.assertj.core.groups.Properties;
 import org.assertj.core.groups.Tuple;
-import org.assertj.core.internal.Failures;
 import org.assertj.core.presentation.BinaryRepresentation;
 import org.assertj.core.presentation.HexadecimalRepresentation;
 import org.assertj.core.presentation.Representation;
@@ -1334,25 +1330,6 @@ public static <T extends Throwable> AbstractThrowableAssert<?, T> then(T actual)
     return assertThat(catchThrowable(shouldRaiseThrowable)).hasBeenThrown();
   }
 
-  /**
-   * Similar to {@link #thenThrownBy(ThrowingCallable)}, but when the called code returns a value instead of
-   * throwing, the assertion error shows the returned value to help understand what went wrong.
-   *
-   * @param shouldRaiseThrowable The {@link ThrowingCallableWithValue} or lambda with the code that should raise the throwable.
-   * @return the created {@link ThrowableAssert}.
-   * @since 3.25.0
-   */
-  @CanIgnoreReturnValue
-  public static AbstractThrowableAssert<?, ? extends Throwable> thenThrownBy(ThrowingCallableWithValue shouldRaiseThrowable) {
-    Object value;
-    try {
-      value = shouldRaiseThrowable.call();
-    } catch (Throwable throwable) {
-      return assertThat(throwable);
-    }
-    throw Failures.instance().failure(format("Expecting code to raise a throwable, but it returned [%s] instead", value));
-  }
-
   /**
    * Allows to capture and then assert on a {@link Throwable} like {@code thenThrownBy(ThrowingCallable)} but this method
    * let you set the assertion description the same way you do with {@link AbstractAssert#as(String, Object...) as(String, Object...)}.
@@ -1390,26 +1367,6 @@ public static <T extends Throwable> AbstractThrowableAssert<?, T> then(T actual)
     return assertThat(catchThrowable(shouldRaiseThrowable)).as(description, args).hasBeenThrown();
   }
 
-  /**
-   * Similar to {@link #thenThrownBy(ThrowingCallable, String, Object...)}, but when the called code returns a value instead of
-   * throwing, the assertion error shows the returned value to help understand what went wrong.
-   *
-   * @param shouldRaiseThrowable The {@link ThrowingCallableWithValue} or lambda with the code that should raise the throwable.
-   * @return the created {@link ThrowableAssert}.
-   * @since 3.25.0
-   */
-  @CanIgnoreReturnValue
-  public static AbstractThrowableAssert<?, ? extends Throwable> thenThrownBy(ThrowingCallableWithValue shouldRaiseThrowable,
-                                                                             String description, Object... args) {
-    Object value;
-    try {
-      value = shouldRaiseThrowable.call();
-    } catch (Throwable throwable) {
-      return assertThat(throwable).as(description, args);
-    }
-    throw Failures.instance().failure(format("Expecting code to raise a throwable, but it returned [%s] instead", value));
-  }
-
   /**
    * Allows to capture and then assert on a {@link Throwable} more easily when used with Java 8 lambdas.
    *
diff --git a/assertj-core/src/main/java/org/assertj/core/api/ThrowableAssert.java b/assertj-core/src/main/java/org/assertj/core/api/ThrowableAssert.java
--- a/assertj-core/src/main/java/org/assertj/core/api/ThrowableAssert.java
+++ b/assertj-core/src/main/java/org/assertj/core/api/ThrowableAssert.java
@@ -33,10 +33,6 @@ public interface ThrowingCallable {
     void call() throws Throwable;
   }
 
-  public interface ThrowingCallableWithValue {
-    Object call() throws Throwable;
-  }
-
   public ThrowableAssert(ACTUAL actual) {
     super(actual, ThrowableAssert.class);
   }


Test patch:
diff --git a/assertj-core/src/test/java/org/assertj/core/api/Assertions_assertThatThrownBy_Test.java b/assertj-core/src/test/java/org/assertj/core/api/Assertions_assertThatThrownBy_Test.java
--- a/assertj-core/src/test/java/org/assertj/core/api/Assertions_assertThatThrownBy_Test.java
+++ b/assertj-core/src/test/java/org/assertj/core/api/Assertions_assertThatThrownBy_Test.java
@@ -14,9 +14,8 @@
 
 import static java.lang.String.format;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.assertj.core.api.Assertions_catchThrowableOfType_Test.raisingException;
-import static org.assertj.core.api.Assertions_catchThrowable_Test.codeThrowing;
 import static org.assertj.core.api.BDDAssertions.then;
+import static org.assertj.core.error.ShouldHaveMessage.shouldHaveMessage;
 import static org.assertj.core.util.AssertionsUtil.expectAssertionError;
 
 import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
@@ -25,47 +24,63 @@
 class Assertions_assertThatThrownBy_Test {
 
   @Test
-  void should_build_ThrowableAssert_with_runtime_exception_thrown() {
+  void should_work_with_runtime_exception_thrown() {
+    // WHEN/THEN
     assertThatThrownBy(codeThrowing(new IllegalArgumentException("boom"))).isInstanceOf(IllegalArgumentException.class)
                                                                           .hasMessage("boom");
   }
 
   @Test
-  void should_build_ThrowableAssert_with_throwable_thrown() {
+  void should_work_with_throwable_thrown() {
+    // WHEN/THEN
     assertThatThrownBy(codeThrowing(new Throwable("boom"))).isInstanceOf(Throwable.class)
                                                            .hasMessage("boom");
   }
 
   @Test
-  void should_be_able_to_pass_a_description_to_assertThatThrownBy() {
+  void should_work_with_method_reference_having_vararg_parameter() {
+    // WHEN/THEN
+    assertThatThrownBy(Assertions_assertThatThrownBy_Test::methodThrowing).isInstanceOf(Exception.class)
+                                                                          .hasMessage("boom");
+  }
+
+  @Test
+  void should_support_description() {
     // GIVEN
-    // make assertThatThrownBy fail to verify the description afterwards
-    ThrowingCallable code = () -> assertThatThrownBy(raisingException("boom"), "Test %s", "code").hasMessage("bam");
+    Throwable throwable = new Exception("boom");
     // WHEN
-    AssertionError assertionError = expectAssertionError(code);
+    AssertionError assertionError = expectAssertionError(() -> assertThatThrownBy(codeThrowing(throwable), "Test %s",
+                                                                                  "code").hasMessage("bam"));
     // THEN
     then(assertionError).hasMessageContaining("[Test code]");
   }
 
   @Test
   void should_fail_if_no_throwable_was_thrown() {
-    // GIVEN
-    ThrowingCallable code = () -> {};
     // WHEN
-    AssertionError assertionError = expectAssertionError(() -> assertThatThrownBy(code).hasMessage("boom ?"));
+    AssertionError assertionError = expectAssertionError(() -> assertThatThrownBy(() -> {}).hasMessage("boom ?"));
     // THEN
     then(assertionError).hasMessage(format("%nExpecting code to raise a throwable."));
   }
 
   @Test
-  void should_fail_with_good_message_when_assertion_is_failing() {
+  void should_fail_with_proper_message_when_assertion_is_failing() {
+    // GIVEN
+    Throwable throwable = new Exception("boom");
     // WHEN
-    AssertionError assertionError = expectAssertionError(() -> assertThatThrownBy(raisingException("boom")).hasMessage("bam"));
+    AssertionError assertionError = expectAssertionError(() -> assertThatThrownBy(codeThrowing(throwable)).hasMessage("bam"));
     // THEN
-    then(assertionError).hasMessageContainingAll("Expecting message to be:",
-                                                 "\"bam\"",
-                                                 "but was:",
-                                                 "\"boom\"");
+    then(assertionError).hasMessage(shouldHaveMessage(throwable, "bam").create());
+  }
+
+  private static ThrowingCallable codeThrowing(Throwable t) {
+    return () -> {
+      throw t;
+    };
+  }
+
+  private static void methodThrowing(Object... parameters) throws Exception {
+    throw new Exception("boom");
   }
 
 }
diff --git a/assertj-core/src/test/java/org/assertj/core/api/throwable/ThrowableAssert_built_from_ThrowingCallable_Test.java b/assertj-core/src/test/java/org/assertj/core/api/throwable/ThrowableAssert_built_from_ThrowingCallable_Test.java
--- a/assertj-core/src/test/java/org/assertj/core/api/throwable/ThrowableAssert_built_from_ThrowingCallable_Test.java
+++ b/assertj-core/src/test/java/org/assertj/core/api/throwable/ThrowableAssert_built_from_ThrowingCallable_Test.java
@@ -12,10 +12,8 @@
  */
 package org.assertj.core.api.throwable;
 
-import static java.lang.String.format;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.assertj.core.util.AssertionsUtil.assertThatAssertionErrorIsThrownBy;
 
 import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
 import org.junit.jupiter.api.Test;
@@ -52,14 +50,7 @@ public void call() {
           // no exception
         }
       });
-    }).withMessage(format("%nExpecting code to raise a throwable."));
+    }).withMessage(String.format("%nExpecting code to raise a throwable."));
   }
 
-  @Test
-  void should_fail_and_show_value_returned_by_callable_code() {
-    // GIVEN
-    ThrowingCallable code = () -> assertThatThrownBy(() -> 42);
-    // WHEN/THEN
-    assertThatAssertionErrorIsThrownBy(code).withMessage("Expecting code to raise a throwable, but it returned [42] instead");
-  }
 }
diff --git a/assertj-core/src/test/java/org/assertj/core/api/throwable/ThrowableAssert_built_with_then_method_Test.java b/assertj-core/src/test/java/org/assertj/core/api/throwable/ThrowableAssert_built_with_then_method_Test.java
--- a/assertj-core/src/test/java/org/assertj/core/api/throwable/ThrowableAssert_built_with_then_method_Test.java
+++ b/assertj-core/src/test/java/org/assertj/core/api/throwable/ThrowableAssert_built_with_then_method_Test.java
@@ -14,11 +14,11 @@
 
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 import static org.assertj.core.api.BDDAssertions.thenThrownBy;
-import static org.assertj.core.util.AssertionsUtil.assertThatAssertionErrorIsThrownBy;
 
 import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
 import org.junit.jupiter.api.Test;
 
+// TODO build two throwable assert with then and assertThat and compare them.
 class ThrowableAssert_built_with_then_method_Test {
 
   @Test
@@ -54,11 +54,4 @@ public void call() {
     }).withMessage(String.format("%nExpecting code to raise a throwable."));
   }
 
-  @Test
-  void should_fail_if_value_is_returned_by_callable_code() {
-    // GIVEN
-    ThrowingCallable code = () -> thenThrownBy(() -> 42);
-    // WHEN/THEN
-    assertThatAssertionErrorIsThrownBy(code).withMessage("Expecting code to raise a throwable, but it returned [42] instead");
-  }
 }