Back to Repositories

Testing OnEditorAction Annotation Processing in ButterKnife

This test suite validates the OnEditorAction functionality in ButterKnife, focusing on view binding and event handling for TextView editor actions. It ensures proper annotation processing and generated binding code for both boolean and void return types.

Test Coverage Overview

The test suite provides comprehensive coverage of the @OnEditorAction annotation functionality:

  • Tests editor action binding with boolean return values
  • Validates default return value behavior for void methods
  • Ensures proper view binding and unbinding lifecycle
  • Verifies correct generation of ViewBinding classes

Implementation Analysis

The testing approach utilizes JUnit and Google’s compile-testing framework to validate annotation processing and code generation. It employs mock source files to test different implementation scenarios and verifies the generated binding code structure and behavior.

The tests specifically focus on the annotation processor’s ability to generate appropriate OnEditorActionListener implementations with proper method signatures and return values.

Technical Details

Key technical components include:

  • ButterKnifeProcessor for annotation processing
  • JavaFileObjects for source code manipulation
  • Compile-testing framework for validation
  • Custom assertion chains for compiler output verification
  • TextView.OnEditorActionListener interface implementation testing

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases for different return type scenarios
  • Comprehensive verification of generated code
  • Clear separation of test resources and assertions
  • Proper handling of compiler options and warnings
  • Thorough validation of binding lifecycle methods

jakewharton/butterknife

butterknife-runtime/src/test/java/butterknife/OnEditorActionTest.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 OnEditorActionTest {
  @Test public void editorAction() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import butterknife.OnEditorAction;\n"
        + "public class Test {\n"
        + "  @OnEditorAction(1) boolean doStuff() { return false; }\n"
        + "}"
    );

    JavaFileObject bindingSource = JavaFileObjects.forSourceString("test/Test_ViewBinding", ""
        + "package test;\n"
        + "import android.view.KeyEvent;\n"
        + "import android.view.View;\n"
        + "import android.widget.TextView;\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"
        + "  private View view1;\n"
        + "  @UiThread\n"
        + "  public Test_ViewBinding(final Test target, View source) {\n"
        + "    this.target = target;\n"
        + "    View view;\n"
        + "    view = Utils.findRequiredView(source, 1, \"method 'doStuff'\");\n"
        + "    view1 = view;\n"
        + "    ((TextView) view).setOnEditorActionListener(new TextView.OnEditorActionListener() {\n"
        + "      @Override\n"
        + "      public boolean onEditorAction(TextView p0, int p1, KeyEvent p2) {\n"
        + "        return target.doStuff();\n"
        + "      }\n"
        + "    });\n"
        + "  }\n"
        + "  @Override\n"
        + "  @CallSuper\n"
        + "  public void unbind() {\n"
        + "    if (target == null) throw new IllegalStateException(\"Bindings already cleared.\");\n"
        + "    target = null;\n"
        + "    ((TextView) view1).setOnEditorActionListener(null);\n"
        + "    view1 = null;\n"
        + "  }\n"
        + "}"
    );

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

  @Test public void defaultReturnValue() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import butterknife.OnEditorAction;\n"
        + "public class Test {\n"
        + "  @OnEditorAction(1) void doStuff() {}\n"
        + "}"
    );

    JavaFileObject bindingSource = JavaFileObjects.forSourceString("test/Test_ViewBinding", ""
        + "package test;\n"
        + "import android.view.KeyEvent;\n"
        + "import android.view.View;\n"
        + "import android.widget.TextView;\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"
        + "  private View view1;\n"
        + "  @UiThread\n"
        + "  public Test_ViewBinding(final Test target, View source) {\n"
        + "    this.target = target;\n"
        + "    View view;\n"
        + "    view = Utils.findRequiredView(source, 1, \"method 'doStuff'\");\n"
        + "    view1 = view;\n"
        + "    ((TextView) view).setOnEditorActionListener(new TextView.OnEditorActionListener() {\n"
        + "      @Override\n"
        + "      public boolean onEditorAction(TextView p0, int p1, KeyEvent p2) {\n"
        + "        target.doStuff();\n"
        + "        return true;\n"
        + "      }\n"
        + "    });\n"
        + "  }\n"
        + "  @Override\n"
        + "  @CallSuper\n"
        + "  public void unbind() {\n"
        + "    if (target == null) throw new IllegalStateException(\"Bindings already cleared.\");\n"
        + "    target = null;\n"
        + "    ((TextView) view1).setOnEditorActionListener(null);\n"
        + "    view1 = null;\n"
        + "  }\n"
        + "}"
    );

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