Repository: assertj/assertj

Problem statement:
Allow String.format for test description
Instead of: 

``` java
assertThat(itemCount).as("Check " + item + " count").isEqualTo(5);
```

We would write:

``` java
assertThat(itemCount).as("Check %s count", item).isEqualTo(5);
```



Hints:

Patch:
diff --git a/src/main/java/org/assertj/core/api/AbstractAssert.java b/src/main/java/org/assertj/core/api/AbstractAssert.java
--- a/src/main/java/org/assertj/core/api/AbstractAssert.java
+++ b/src/main/java/org/assertj/core/api/AbstractAssert.java
@@ -80,7 +80,7 @@ protected WritableAssertionInfo getWritableAssertionInfo() {
    * Utility method to ease write write custom assertions classes, you can use format specifiers in error message, they
    * will be replaced by the given arguments.
    * <p>
-   * Moreover, this method honors any description ({@link #as(String)} or overridden error message defined by the user (
+   * Moreover, this method honors any description ({@link #as(String, Object...)} or overridden error message defined by the user (
    * {@link #overridingErrorMessage(String, Object...)}.
    * <p>
    * Example :
@@ -109,8 +109,8 @@ protected void failWithMessage(String errorMessage, Object... arguments) {
 
   /** {@inheritDoc} */
   @Override
-  public S as(String description) {
-    return describedAs(description);
+  public S as(String description, Object... args) {
+    return describedAs(description, args);
   }
 
   /** {@inheritDoc} */
@@ -121,8 +121,8 @@ public S as(Description description) {
 
   /** {@inheritDoc} */
   @Override
-  public S describedAs(String description) {
-    info.description(description);
+  public S describedAs(String description, Object... args) {
+    info.description(description, args);
     return myself;
   }
 
@@ -301,7 +301,7 @@ public S isNotOfAnyClassIn(Class<?>... types) {
   }
 
   /**
-   * The description of this assertion set with {@link #describedAs(String)} or {@link #describedAs(Description)}.
+   * The description of this assertion set with {@link #describedAs(String, Object...)} or {@link #describedAs(Description)}.
    * 
    * @return the description String representation of this assertion.
    */
diff --git a/src/main/java/org/assertj/core/api/Condition.java b/src/main/java/org/assertj/core/api/Condition.java
--- a/src/main/java/org/assertj/core/api/Condition.java
+++ b/src/main/java/org/assertj/core/api/Condition.java
@@ -60,14 +60,14 @@ public Condition(Description description) {
 
   /** {@inheritDoc} */
   @Override
-  public Condition<T> describedAs(String newDescription) {
-    return as(newDescription);
+  public Condition<T> describedAs(String newDescription, Object... args) {
+    return as(newDescription, args);
   }
 
   /** {@inheritDoc} */
   @Override
-  public Condition<T> as(String newDescription) {
-    description = checkIsNotNull(newDescription);
+  public Condition<T> as(String newDescription, Object... args) {
+    description = checkIsNotNull(newDescription, args);
     return this;
   }
 
diff --git a/src/main/java/org/assertj/core/api/Descriptable.java b/src/main/java/org/assertj/core/api/Descriptable.java
--- a/src/main/java/org/assertj/core/api/Descriptable.java
+++ b/src/main/java/org/assertj/core/api/Descriptable.java
@@ -30,11 +30,12 @@ public interface Descriptable<S extends Descriptable<S>> {
   /**
    * Sets the description of this object.
    * @param description the new description to set.
+   * @param args optional parameter if description is a format String.
    * @return {@code this} object.
    * @throws NullPointerException if the description is {@code null}.
-   * @see #describedAs(String)
+   * @see #describedAs(String, Object...)
    */
-  S as(String description);
+  S as(String description, Object... args);
 
   /**
    * Sets the description of this object. To remove or clear the description, pass a <code>{@link EmptyTextDescription}</code> as
@@ -52,16 +53,16 @@ public interface Descriptable<S extends Descriptable<S>> {
   S as(Description description);
 
   /**
-   * Alias for <code>{@link #as(String)}</code> since "as" is a keyword in <a href="http://groovy.codehaus.org/"
+   * Alias for <code>{@link #as(String, Object...)}</code> since "as" is a keyword in <a href="http://groovy.codehaus.org/"
    * target="_blank">Groovy</a>.
    * @param description the new description to set.
    * @return {@code this} object.
    * @throws NullPointerException if the description is {@code null}.
    */
-  S describedAs(String description);
+  S describedAs(String description, Object... args);
 
   /**
-   * Alias for <code>{@link #as(String)}</code> since "as" is a keyword in <a href="http://groovy.codehaus.org/"
+   * Alias for <code>{@link #as(String, Object...)}</code> since "as" is a keyword in <a href="http://groovy.codehaus.org/"
    * target="_blank">Groovy</a>. To remove or clear the description, pass a <code>{@link EmptyTextDescription}</code> as argument.
    * <p>
    * This overloaded version of "describedAs" offers more flexibility than the one taking a {@code String} by allowing users to
diff --git a/src/main/java/org/assertj/core/api/DescriptionValidations.java b/src/main/java/org/assertj/core/api/DescriptionValidations.java
--- a/src/main/java/org/assertj/core/api/DescriptionValidations.java
+++ b/src/main/java/org/assertj/core/api/DescriptionValidations.java
@@ -22,9 +22,9 @@
  */
 final class DescriptionValidations {
 
-  static Description checkIsNotNull(String d) {
+  static Description checkIsNotNull(String d, Object... args) {
     if (d == null) throw bomb();
-    return new TextDescription(d);
+    return new TextDescription(d, args);
   }
 
   static Description checkIsNotNull(Description d) {
diff --git a/src/main/java/org/assertj/core/api/WritableAssertionInfo.java b/src/main/java/org/assertj/core/api/WritableAssertionInfo.java
--- a/src/main/java/org/assertj/core/api/WritableAssertionInfo.java
+++ b/src/main/java/org/assertj/core/api/WritableAssertionInfo.java
@@ -65,11 +65,12 @@ public String descriptionText() {
   /**
    * Sets the description of an assertion.
    * @param newDescription the new description.
+   * @param args if {@code newDescription} is a format String, {@code args} is argument of {@link String#format(String, Object...)}
    * @throws NullPointerException if the given description is {@code null}.
    * @see #description(Description)
    */
-  public void description(String newDescription) {
-    description = checkIsNotNull(newDescription);
+  public void description(String newDescription, Object... args) {
+    description = checkIsNotNull(newDescription, args);
   }
 
   /**
diff --git a/src/main/java/org/assertj/core/description/TextDescription.java b/src/main/java/org/assertj/core/description/TextDescription.java
--- a/src/main/java/org/assertj/core/description/TextDescription.java
+++ b/src/main/java/org/assertj/core/description/TextDescription.java
@@ -14,7 +14,11 @@
  */
 package org.assertj.core.description;
 
-import static org.assertj.core.util.Objects.*;
+import java.util.regex.Pattern;
+import org.assertj.core.util.Arrays;
+import static org.assertj.core.util.Objects.HASH_CODE_PRIME;
+import static org.assertj.core.util.Objects.areEqual;
+import static org.assertj.core.util.Objects.hashCodeFor;
 import static org.assertj.core.util.Preconditions.checkNotNull;
 
 import org.assertj.core.util.VisibleForTesting;
@@ -22,33 +26,40 @@
 
 /**
  * A text-based description.
- * 
+ *
  * @author Yvonne Wang
  * @author Alex Ruiz
+ * @author William Delanoue
  */
 public class TextDescription extends Description {
   @VisibleForTesting
   final String value;
 
+  final Object[] args;
+
   /**
    * Creates a new </code>{@link TextDescription}</code>.
-   * 
+   *
    * @param value the value of this description.
    * @throws NullPointerException if the given value is {@code null}.
    */
-  public TextDescription(String value) {
+  public TextDescription(String value, Object... args) {
     checkNotNull(value);
     this.value = value;
+    this.args = Arrays.isNullOrEmpty(args) ? null : args.clone();
   }
 
   @Override
   public String value() {
+    if(args != null) {
+      return org.assertj.core.util.Strings.formatIfArgs(value.replaceAll("%", "%%").replaceAll(Pattern.quote("{}"), "%s"), args);
+    }
     return value;
   }
 
   @Override
   public int hashCode() {
-    return HASH_CODE_PRIME * 1 + hashCodeFor(value);
+    return HASH_CODE_PRIME * 1 + hashCodeFor(value) + hashCodeFor(args);
   }
 
   @Override
@@ -63,6 +74,6 @@ public boolean equals(Object obj) {
       return false;
     }
     TextDescription other = (TextDescription) obj;
-    return areEqual(value, other.value);
+    return areEqual(value, other.value) && areEqual(args, other.args);
   }
 }


Test patch:
diff --git a/src/test/java/org/assertj/core/description/TextDescription_toString_Test.java b/src/test/java/org/assertj/core/description/TextDescription_toString_Test.java
--- a/src/test/java/org/assertj/core/description/TextDescription_toString_Test.java
+++ b/src/test/java/org/assertj/core/description/TextDescription_toString_Test.java
@@ -32,4 +32,10 @@ public void should_return_value() {
     TextDescription description = new TextDescription("Flash");
     assertEquals(description.value, description.toString());
   }
+
+  @Test
+  public void should_return_formatted_value() {
+    TextDescription description = new TextDescription("Flash %s {}", "MacQueen");
+    assertEquals("Flash %s MacQueen", description.toString());
+  }
 }
diff --git a/src/test/java/org/assertj/core/description/TextDescription_value_Test.java b/src/test/java/org/assertj/core/description/TextDescription_value_Test.java
--- a/src/test/java/org/assertj/core/description/TextDescription_value_Test.java
+++ b/src/test/java/org/assertj/core/description/TextDescription_value_Test.java
@@ -32,4 +32,10 @@ public void should_return_value() {
     TextDescription description = new TextDescription("Robin");
     assertEquals(description.value, description.value());
   }
+
+  @Test
+  public void should_return_formatted_value() {
+    TextDescription description = new TextDescription("Robin {}", "Hood");
+    assertEquals("Robin Hood", description.value());
+  }
 }