Back to Repositories

Testing BindDrawable Annotation Processing in ButterKnife

This test suite validates the BindDrawable annotation functionality in ButterKnife, focusing on drawable resource binding and tinting capabilities. The tests ensure proper compile-time validation and runtime behavior of drawable bindings across different Android SDK versions.

Test Coverage Overview

The test suite provides comprehensive coverage of BindDrawable annotation functionality:

  • Basic drawable binding for SDK 21+ implementations
  • Drawable tinting capabilities and resource handling
  • Type safety validation for drawable fields
  • Generated binding code verification

Implementation Analysis

The testing approach utilizes compile-time verification through the ButterKnifeProcessor:

Tests validate both successful compilation scenarios and proper error handling using JavaFileObjects for source generation. The implementation leverages Google’s compile-testing framework for annotation processing verification.

Technical Details

Key technical components include:

  • JUnit test framework for test organization
  • Google Truth assertions for compile-time validation
  • JavaFileObjects for source code manipulation
  • Compiler options configuration for SDK version testing
  • ButterKnifeProcessor for annotation processing

Best Practices Demonstrated

The test suite exhibits several testing best practices:

  • Isolated test cases for distinct functionality
  • Comprehensive error case validation
  • Generated code verification
  • Clear test method naming
  • Proper setup of compiler options and processing environment

jakewharton/butterknife

butterknife-runtime/src/test/java/butterknife/BindDrawableTest.java

            
package butterknife;

import butterknife.compiler.ButterKnifeProcessor;
import com.google.testing.compile.JavaFileObjects;
import javax.tools.JavaFileObject;
import org.junit.Test;

import static com.google.common.truth.Truth.assertAbout;
import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;

public final class BindDrawableTest {
  @Test public void simpleSdk21() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import android.graphics.drawable.Drawable;\n"
        + "import butterknife.BindDrawable;\n"
        + "public class Test {\n"
        + "  @BindDrawable(1) Drawable one;\n"
        + "}"
    );

    JavaFileObject bindingSource = JavaFileObjects.forSourceString("test/Test_ViewBinding", ""
        + "// Generated code from Butter Knife. Do not modify!\n"
        + "package test;\n"
        + "import android.content.Context;\n"
        + "import android.view.View;\n"
        + "import androidx.annotation.CallSuper;\n"
        + "import androidx.annotation.UiThread;\n"
        + "import butterknife.Unbinder;\n"
        + "import java.lang.Deprecated;\n"
        + "import java.lang.Override;\n"
        + "import java.lang.SuppressWarnings;\n"
        + "public class Test_ViewBinding implements Unbinder {\n"
        + "  /**\n"
        + "   * @deprecated Use {@link #Test_ViewBinding(Test, Context)} for direct creation.\n"
        + "   *     Only present for runtime invocation through {@code ButterKnife.bind()}.\n"
        + "   */\n"
        + "  @Deprecated\n"
        + "  @UiThread\n"
        + "  public Test_ViewBinding(Test target, View source) {\n"
        + "    this(target, source.getContext());\n"
        + "  }\n"
        + "  @UiThread\n"
        + "  @SuppressWarnings(\"ResourceType\")\n"
        + "  public Test_ViewBinding(Test target, Context context) {\n"
        + "    target.one = context.getDrawable(1);\n"
        + "  }\n"
        + "  @Override\n"
        + "  @CallSuper\n"
        + "  public void unbind() {\n"
        + "  }\n"
        + "}"
    );

    assertAbout(javaSource()).that(source)
        .withCompilerOptions("-Xlint:-processing", "-Abutterknife.minSdk=21")
        .processedWith(new ButterKnifeProcessor())
        .compilesWithoutWarnings()
        .and()
        .generatesSources(bindingSource);
  }

  @Test public void withTint() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import android.graphics.drawable.Drawable;\n"
        + "import butterknife.BindDrawable;\n"
        + "public class Test {\n"
        + "  @BindDrawable(value = 1, tint = 2) Drawable one;\n"
        + "}"
    );

    JavaFileObject bindingSource = JavaFileObjects.forSourceString("test/Test_ViewBinding", ""
        + "// Generated code from Butter Knife. Do not modify!\n"
        + "package test;\n"
        + "import android.content.Context;\n"
        + "import android.view.View;\n"
        + "import androidx.annotation.CallSuper;\n"
        + "import androidx.annotation.UiThread;\n"
        + "import butterknife.Unbinder;\n"
        + "import butterknife.internal.Utils;\n"
        + "import java.lang.Deprecated;\n"
        + "import java.lang.Override;\n"
        + "import java.lang.SuppressWarnings;\n"
        + "public class Test_ViewBinding implements Unbinder {\n"
        + "  /**\n"
        + "   * @deprecated Use {@link #Test_ViewBinding(Test, Context)} for direct creation.\n"
        + "   *     Only present for runtime invocation through {@code ButterKnife.bind()}.\n"
        + "   */\n"
        + "  @Deprecated\n"
        + "  @UiThread\n"
        + "  public Test_ViewBinding(Test target, View source) {\n"
        + "    this(target, source.getContext());\n"
        + "  }\n"
        + "  @UiThread\n"
        + "  @SuppressWarnings(\"ResourceType\")\n"
        + "  public Test_ViewBinding(Test target, Context context) {\n"
        + "    target.one = Utils.getTintedDrawable(context, 1, 2);\n"
        + "  }\n"
        + "  @Override\n"
        + "  @CallSuper\n"
        + "  public void unbind() {\n"
        + "  }\n"
        + "}"
    );

    assertAbout(javaSource()).that(source)
        .withCompilerOptions("-Xlint:-processing")
        .processedWith(new ButterKnifeProcessor())
        .compilesWithoutWarnings()
        .and()
        .generatesSources(bindingSource);
  }

  @Test public void typeMustBeDrawable() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import butterknife.BindDrawable;\n"
        + "public class Test {\n"
        + "  @BindDrawable(1) String one;\n"
        + "}"
    );

    assertAbout(javaSource()).that(source)
        .processedWith(new ButterKnifeProcessor())
        .failsToCompile()
        .withErrorContaining("@BindDrawable field type must be 'Drawable'. (test.Test.one)")
        .in(source).onLine(4);
  }
}