Back to Repositories

Testing OnTouch Event Binding Implementation in ButterKnife

This test suite validates the OnTouch functionality in ButterKnife’s view binding system, focusing on touch event handling and listener implementations. It ensures proper generation of view bindings for touch events and verifies correct behavior of return values and multiple listener scenarios.

Test Coverage Overview

The test suite provides comprehensive coverage of OnTouch annotations in ButterKnife:

  • Basic touch event binding with boolean return values
  • Default return value handling for void methods
  • Error cases for multiple touch listeners
  • View binding lifecycle management

Implementation Analysis

The testing approach utilizes compile-time verification through the ButterKnifeProcessor. It employs JavaFileObjects for source generation testing and validates the correct generation of binding classes. The tests specifically verify annotation processing outcomes and generated code structure.

Key patterns include source file comparison, compiler option configuration, and error message validation.

Technical Details

Testing tools and configuration:

  • JUnit test framework
  • Google Testing Compile utilities
  • Java Source Subject Factory for compilation testing
  • Custom compiler options (-Xlint:-processing)
  • ButterKnifeProcessor for annotation processing

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases for different scenarios
  • Explicit verification of generated code
  • Comprehensive error case handling
  • Clear test method naming
  • Proper setup of compile-time testing environment

jakewharton/butterknife

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

    JavaFileObject bindingSource = JavaFileObjects.forSourceString("test/Test_ViewBinding", ""
        + "package test;\n"
        + "import android.annotation.SuppressLint;\n"
        + "import android.view.MotionEvent;\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"
        + "  private View view1;\n"
        + "  @UiThread\n"
        + "  @SuppressLint(\"ClickableViewAccessibility\")\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"
        + "    view.setOnTouchListener(new View.OnTouchListener() {\n"
        + "      @Override\n"
        + "      public boolean onTouch(View p0, MotionEvent p1) {\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"
        + "    view1.setOnTouchListener(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.OnTouch;\n"
        + "public class Test {\n"
        + "  @OnTouch(1) void doStuff() {}\n"
        + "}"
    );

    JavaFileObject bindingSource = JavaFileObjects.forSourceString("test/Test_ViewBinding", ""
        + "package test;\n"
        + "import android.annotation.SuppressLint;\n"
        + "import android.view.MotionEvent;\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"
        + "  private View view1;\n"
        + "  @UiThread\n"
        + "  @SuppressLint(\"ClickableViewAccessibility\")\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"
        + "    view.setOnTouchListener(new View.OnTouchListener() {\n"
        + "      @Override\n"
        + "      public boolean onTouch(View p0, MotionEvent p1) {\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"
        + "    view1.setOnTouchListener(null);\n"
        + "    view1 = null;\n"
        + "  }\n"
        + "}"
    );

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

  @Test public void failsMultipleListenersWithReturnValue() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import butterknife.OnTouch;\n"
        + "public class Test {\n"
        + "  @OnTouch(1) boolean doStuff1() {}\n"
        + "  @OnTouch(1) boolean doStuff2() {}\n"
        + "}"
    );

    assertAbout(javaSource()).that(source)
        .processedWith(new ButterKnifeProcessor())
        .failsToCompile()
        .withErrorContaining(
            "Multiple listener methods with return value specified for ID 1. (test.Test.doStuff2)")
        .in(source).onLine(5);
  }
}