Back to Repositories

Testing Activity Binding Code Generation in ButterKnife

This test suite validates the functionality of ButterKnife’s view and resource binding capabilities in Android Activities. It focuses on testing the code generation for both resource bindings and view bindings, ensuring proper implementation of the Unbinder interface.

Test Coverage Overview

The test suite provides comprehensive coverage of ButterKnife’s binding functionality in Android Activities.

Key areas tested include:
  • Resource binding with @BindBool annotation
  • View binding with @BindView annotation
  • Generated binding class implementation
  • Proper unbinding behavior

Implementation Analysis

The testing approach utilizes compile-time verification through the JavaFileObjects testing utility. It validates the generated code against expected implementations, ensuring correct binding class generation for both resource and view bindings.

The tests specifically verify:
  • Correct package and import declarations
  • Proper annotation processing
  • Implementation of required interfaces
  • Thread safety annotations

Technical Details

Testing tools and configuration:
  • JUnit test framework
  • ButterKnifeProcessor for annotation processing
  • Google Testing Compile utilities
  • Java Source Subject Factory for compilation verification
  • Compiler options configured to suppress processing warnings

Best Practices Demonstrated

The test suite exemplifies several testing best practices for annotation processors and code generation.

Notable practices include:
  • Separation of concerns between resource and view binding tests
  • Explicit verification of generated source code
  • Comprehensive validation of thread safety annotations
  • Proper error handling verification
  • Clear test case organization and naming

jakewharton/butterknife

butterknife-runtime/src/test/java/butterknife/ExtendActivityTest.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 class ExtendActivityTest {
  @Test public void onlyResources() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;"
        + "import android.app.Activity;"
        + "import android.content.Context;"
        + "import butterknife.BindBool;"
        + "public class Test extends Activity {"
        + "  @BindBool(1) boolean one;"
        + "}"
    );

    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.content.res.Resources;\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"
        + "  @UiThread\n"
        + "  public Test_ViewBinding(Test target) {\n"
        + "    this(target, target);\n"
        + "  }\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"
        + "    Resources res = context.getResources();\n"
        + "    target.one = res.getBoolean(1);\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 views() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;"
        + "import android.app.Activity;"
        + "import android.content.Context;"
        + "import android.view.View;"
        + "import butterknife.BindView;"
        + "public class Test extends Activity {"
        + "  @BindView(1) View one;"
        + "}"
    );

    JavaFileObject bindingSource = JavaFileObjects.forSourceString("test/Test_ViewBinding", ""
        + "package test;\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.IllegalStateException;\n"
        + "import java.lang.Override;\n"
        + "public class Test_ViewBinding implements Unbinder {\n"
        + "  private Test target;\n"
        + "  @UiThread\n"
        + "  public Test_ViewBinding(Test target) {\n"
        + "    this(target, target.getWindow().getDecorView());\n"
        + "  }\n"
        + "  @UiThread\n"
        + "  public Test_ViewBinding(Test target, View source) {\n"
        + "    this.target = target;\n"
        + "    target.one = Utils.findRequiredView(source, 1, \"field 'one'\");\n"
        + "  }\n"
        + "  @Override\n"
        + "  @CallSuper\n"
        + "  public void unbind() {\n"
        + "    Test target = this.target;\n"
        + "    if (target == null) throw new IllegalStateException(\"Bindings already cleared.\");\n"
        + "    this.target = null;\n"
        + "    target.one = null;\n"
        + "  }\n"
        + "}"
    );

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