Back to Repositories

Validating OnLongClick View Binding Integration in ButterKnife

This test suite validates the OnLongClick functionality in ButterKnife’s view binding system, focusing on event handling and view interactions in Android applications. The tests verify various aspects of long-click behavior, method return types, and view binding lifecycle management.

Test Coverage Overview

The test suite provides comprehensive coverage of OnLongClick annotations in ButterKnife.

Key areas tested include:
  • Simple long-click event handling with boolean returns
  • Void return type handlers
  • Method visibility variations (public, package, protected)
  • Multiple view ID binding
  • Optional view binding scenarios
  • View type casting and interface implementations

Implementation Analysis

The testing approach utilizes JUnit with Android instrumentation testing framework. Tests are executed on the UI thread using @UiThreadTest annotation, ensuring proper Android component lifecycle handling.

Key patterns include:
  • View tree creation for simulated UI testing
  • Unbinder lifecycle management
  • Click event simulation and verification
  • View type casting validation

Technical Details

Testing tools and configuration:
  • JUnit test framework
  • AndroidX Test Instrumentation
  • ButterKnife view binding library
  • Custom ViewTree test utility
  • UI thread execution management
  • View hierarchy simulation

Best Practices Demonstrated

The test suite exemplifies robust integration testing practices for Android view binding.

Notable practices include:
  • Systematic test case organization
  • Proper cleanup with unbinder
  • Comprehensive edge case coverage
  • Clear test method naming
  • Effective view hierarchy management
  • Type-safe view binding verification

jakewharton/butterknife

butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/OnLongClickTest.java

            
package com.example.butterknife.functional;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
import butterknife.ButterKnife;
import butterknife.OnLongClick;
import butterknife.Optional;
import butterknife.Unbinder;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

@SuppressWarnings("unused") // Used reflectively / by code gen.
public final class OnLongClickTest {
  static final class Simple {
    boolean returnValue = true;
    int clicks = 0;

    @OnLongClick(1) boolean click() {
      clicks++;
      return returnValue;
    }
  }

  @UiThreadTest
  @Test public void simple() {
    View tree = ViewTree.create(1);
    View view1 = tree.findViewById(1);

    Simple target = new Simple();
    Unbinder unbinder = ButterKnife.bind(target, tree);
    assertEquals(0, target.clicks);

    assertTrue(view1.performLongClick());
    assertEquals(1, target.clicks);

    target.returnValue = false;
    assertFalse(view1.performLongClick());
    assertEquals(2, target.clicks);

    unbinder.unbind();
    view1.performLongClick();
    assertEquals(2, target.clicks);
  }

  static final class ReturnVoid {
    int clicks = 0;

    @OnLongClick(1) void click() {
      clicks++;
    }
  }

  @UiThreadTest
  @Test public void returnVoid() {
    View tree = ViewTree.create(1);
    View view1 = tree.findViewById(1);

    ReturnVoid target = new ReturnVoid();
    Unbinder unbinder = ButterKnife.bind(target, tree);
    assertEquals(0, target.clicks);

    assertTrue(view1.performLongClick());
    assertEquals(1, target.clicks);

    unbinder.unbind();
    view1.performLongClick();
    assertEquals(1, target.clicks);
  }

  static final class Visibilities {
    int clicks = 0;

    @OnLongClick(1) public boolean publicClick() {
      clicks++;
      return true;
    }

    @OnLongClick(2) boolean packageClick() {
      clicks++;
      return true;
    }

    @OnLongClick(3) protected boolean protectedClick() {
      clicks++;
      return true;
    }
  }

  @UiThreadTest
  @Test public void visibilities() {
    View tree = ViewTree.create(1, 2, 3);
    View view1 = tree.findViewById(1);
    View view2 = tree.findViewById(2);
    View view3 = tree.findViewById(3);

    Visibilities target = new Visibilities();
    ButterKnife.bind(target, tree);
    assertEquals(0, target.clicks);

    view1.performLongClick();
    assertEquals(1, target.clicks);

    view2.performLongClick();
    assertEquals(2, target.clicks);

    view3.performLongClick();
    assertEquals(3, target.clicks);
  }

  static final class MultipleIds {
    int clicks = 0;

    @OnLongClick({1, 2}) boolean click() {
      clicks++;
      return true;
    }
  }

  @UiThreadTest
  @Test public void multipleIds() {
    View tree = ViewTree.create(1, 2);
    View view1 = tree.findViewById(1);
    View view2 = tree.findViewById(2);

    MultipleIds target = new MultipleIds();
    Unbinder unbinder = ButterKnife.bind(target, tree);
    assertEquals(0, target.clicks);

    view1.performLongClick();
    assertEquals(1, target.clicks);

    view2.performLongClick();
    assertEquals(2, target.clicks);

    unbinder.unbind();
    view1.performLongClick();
    view2.performLongClick();
    assertEquals(2, target.clicks);
  }

  static final class OptionalId {
    int clicks = 0;

    @Optional @OnLongClick(1) public boolean click() {
      clicks++;
      return true;
    }
  }

  @UiThreadTest
  @Test public void optionalIdPresent() {
    View tree = ViewTree.create(1);
    View view1 = tree.findViewById(1);

    OptionalId target = new OptionalId();
    Unbinder unbinder = ButterKnife.bind(target, tree);
    assertEquals(0, target.clicks);

    view1.performLongClick();
    assertEquals(1, target.clicks);

    unbinder.unbind();
    view1.performLongClick();
    assertEquals(1, target.clicks);
  }

  @UiThreadTest
  @Test public void optionalIdAbsent() {
    View tree = ViewTree.create(2);
    View view2 = tree.findViewById(2);

    OptionalId target = new OptionalId();
    Unbinder unbinder = ButterKnife.bind(target, tree);
    assertEquals(0, target.clicks);

    view2.performLongClick();
    assertEquals(0, target.clicks);

    unbinder.unbind();
    view2.performLongClick();
    assertEquals(0, target.clicks);
  }

  static final class ArgumentCast {
    interface MyInterface {}

    View last;

    @OnLongClick(1) boolean clickView(View view) {
      last = view;
      return true;
    }

    @OnLongClick(2) boolean clickTextView(TextView view) {
      last = view;
      return true;
    }

    @OnLongClick(3) boolean clickButton(Button view) {
      last = view;
      return true;
    }

    @OnLongClick(4) boolean clickMyInterface(MyInterface view) {
      last = (View) view;
      return true;
    }
  }

  @UiThreadTest
  @Test public void argumentCast() {
    class MyView extends Button implements ArgumentCast.MyInterface {
      MyView(Context context) {
        super(context);
      }
    }

    View view1 = new MyView(InstrumentationRegistry.getContext());
    view1.setId(1);
    View view2 = new MyView(InstrumentationRegistry.getContext());
    view2.setId(2);
    View view3 = new MyView(InstrumentationRegistry.getContext());
    view3.setId(3);
    View view4 = new MyView(InstrumentationRegistry.getContext());
    view4.setId(4);
    ViewGroup tree = new FrameLayout(InstrumentationRegistry.getContext());
    tree.addView(view1);
    tree.addView(view2);
    tree.addView(view3);
    tree.addView(view4);

    ArgumentCast target = new ArgumentCast();
    ButterKnife.bind(target, tree);

    view1.performLongClick();
    assertSame(view1, target.last);

    view2.performLongClick();
    assertSame(view2, target.last);

    view3.performLongClick();
    assertSame(view3, target.last);

    view4.performLongClick();
    assertSame(view4, target.last);
  }
}