Back to Repositories

Testing OnItemSelected Event Handling Implementation in ButterKnife

This test suite validates the OnItemSelected functionality in ButterKnife, focusing on spinner selection event handling and view binding. The tests comprehensively verify different binding scenarios, visibility modifiers, and argument casting for spinner selection events.

Test Coverage Overview

The test suite provides extensive coverage of OnItemSelected annotations in ButterKnife:
  • Simple selection and deselection events
  • Multiple bindings to the same view ID
  • Different visibility modifiers (public, package-private, protected)
  • Multiple ID permutations
  • Optional binding scenarios
  • Argument type casting validation

Implementation Analysis

The testing approach utilizes JUnit with UI thread testing annotations for Android components. It implements a TestSpinner class extending AbsSpinner to simulate selection events programmatically. The tests verify both selection and nothing-selected callbacks, binding/unbinding behavior, and proper event propagation.

Technical Details

Testing infrastructure includes:
  • AndroidX Test framework integration
  • UiThreadTest annotations for UI operations
  • Custom TestSpinner implementation for event simulation
  • ButterKnife binding and unbinding verification
  • JUnit assertions for state validation

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Comprehensive edge case coverage
  • Proper test isolation and setup
  • Clear test method naming and organization
  • Thorough verification of binding lifecycle
  • Systematic approach to testing different visibility levels

jakewharton/butterknife

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

            
package com.example.butterknife.functional;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsSpinner;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
import butterknife.ButterKnife;
import butterknife.OnItemSelected;
import butterknife.Optional;
import butterknife.Unbinder;
import com.example.butterknife.BuildConfig;
import com.example.butterknife.library.SimpleAdapter;
import org.junit.Before;
import org.junit.Test;

import static butterknife.OnItemSelected.Callback.NOTHING_SELECTED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assume.assumeFalse;

@SuppressWarnings("unused") // Used by code gen.
public final class OnItemSelectedTest {
  static class TestSpinner extends AbsSpinner {
    public TestSpinner(Context context) {
      super(context);
      setAdapter(new SimpleAdapter(context));
    }

    void performSelection(int position) {
      if (position < 0) {
        return;
      }

      AdapterView.OnItemSelectedListener listener = getOnItemSelectedListener();
      if (listener != null) {
        listener.onItemSelected(this, null, position, NO_ID);
      }
    }

    void clearSelection() {
      AdapterView.OnItemSelectedListener listener = getOnItemSelectedListener();
      if (listener != null) {
        listener.onNothingSelected(this);
      }
    }
  }

  @Before public void ignoreIfReflect() {
    assumeFalse("Not implemented", BuildConfig.FLAVOR.equals("reflect")); // TODO
  }

  static final class Simple {
    int selectedPosition = -1;

    @OnItemSelected(1) void select(int position) {
      selectedPosition = position;
    }

    @OnItemSelected(value = 1, callback = NOTHING_SELECTED) void clear() {
      selectedPosition = -1;
    }
  }

  @UiThreadTest
  @Test public void simple() {
    View tree = ViewTree.create(TestSpinner.class, 1);
    TestSpinner spinner = tree.findViewById(1);

    Simple target = new Simple();
    Unbinder unbinder = ButterKnife.bind(target, tree);
    assertEquals(-1, target.selectedPosition);

    spinner.performSelection(0);
    assertEquals(0, target.selectedPosition);

    spinner.clearSelection();
    assertEquals(-1, target.selectedPosition);

    spinner.performSelection(1);
    unbinder.unbind();
    spinner.performSelection(0);
    assertEquals(1, target.selectedPosition);
    spinner.clearSelection();
    assertEquals(1, target.selectedPosition);
  }

  static final class MultipleBindings {
    int selectedPosition1 = -1;
    int selectedPosition2 = -1;

    @OnItemSelected(1) void select1(int position) {
      selectedPosition1 = position;
    }

    @OnItemSelected(1) void select2(int position) {
      selectedPosition2 = position;
    }

    @OnItemSelected(value = 1, callback = NOTHING_SELECTED) void clear1() {
      selectedPosition1 = -1;
    }

    @OnItemSelected(value = 1, callback = NOTHING_SELECTED) void clear2() {
      selectedPosition2 = -1;
    }
  }

  @UiThreadTest
  @Test public void multipleBindings() {
    View tree = ViewTree.create(TestSpinner.class, 1);
    TestSpinner spinner = tree.findViewById(1);

    MultipleBindings target = new MultipleBindings();
    Unbinder unbinder = ButterKnife.bind(target, tree);
    assertEquals(-1, target.selectedPosition1);
    assertEquals(-1, target.selectedPosition2);

    spinner.performSelection(0);
    assertEquals(0, target.selectedPosition1);
    assertEquals(0, target.selectedPosition2);

    spinner.clearSelection();
    assertEquals(-1, target.selectedPosition1);
    assertEquals(-1, target.selectedPosition2);

    spinner.performSelection(1);
    unbinder.unbind();
    spinner.performSelection(0);
    assertEquals(1, target.selectedPosition1);
    assertEquals(1, target.selectedPosition2);
    spinner.clearSelection();
    assertEquals(1, target.selectedPosition1);
    assertEquals(1, target.selectedPosition2);
  }

  static final class Visibilities {
    int selectedPosition = -1;

    @OnItemSelected(1) public void publicSelect(int position) {
      selectedPosition = position;
    }

    @OnItemSelected(2) void packageSelect(int position) {
      selectedPosition = position;
    }

    @OnItemSelected(3) protected void protectedSelect(int position) {
      selectedPosition = position;
    }

    @OnItemSelected(value = 1, callback = NOTHING_SELECTED) public void publicClear() {
      selectedPosition = -1;
    }

    @OnItemSelected(value = 2, callback = NOTHING_SELECTED) void packageClear() {
      selectedPosition = -1;
    }

    @OnItemSelected(value = 3, callback = NOTHING_SELECTED) protected void protectedClear() {
      selectedPosition = -1;
    }
  }

  @UiThreadTest
  @Test public void visibilities() {
    View tree = ViewTree.create(TestSpinner.class, 1, 2, 3);
    TestSpinner spinner1 = tree.findViewById(1);
    TestSpinner spinner2 = tree.findViewById(2);
    TestSpinner spinner3 = tree.findViewById(3);

    Visibilities target = new Visibilities();
    ButterKnife.bind(target, tree);
    assertEquals(-1, target.selectedPosition);

    spinner1.performSelection(0);
    assertEquals(0, target.selectedPosition);

    spinner1.clearSelection();
    assertEquals(-1, target.selectedPosition);

    spinner2.performSelection(0);
    assertEquals(0, target.selectedPosition);

    spinner2.clearSelection();
    assertEquals(-1, target.selectedPosition);

    spinner3.performSelection(0);
    assertEquals(0, target.selectedPosition);

    spinner3.clearSelection();
    assertEquals(-1, target.selectedPosition);
  }

  static final class MultipleIdPermutation {
    int selectedPosition = -1;

    @OnItemSelected({1, 2}) void select(int position) {
      selectedPosition = position;
    }

    @OnItemSelected(value = {1, 3}, callback = NOTHING_SELECTED) void clear() {
      selectedPosition = -1;
    }
  }

  @UiThreadTest
  @Test public void multipleIdPermutation() {
    View tree = ViewTree.create(TestSpinner.class, 1, 2, 3);
    TestSpinner spinner1 = tree.findViewById(1);
    TestSpinner spinner2 = tree.findViewById(2);
    TestSpinner spinner3 = tree.findViewById(3);

    MultipleIdPermutation target = new MultipleIdPermutation();
    Unbinder unbinder = ButterKnife.bind(target, tree);
    assertEquals(-1, target.selectedPosition);

    spinner1.performSelection(0);
    assertEquals(0, target.selectedPosition);

    spinner1.clearSelection();
    assertEquals(-1, target.selectedPosition);

    spinner2.performSelection(0);
    assertEquals(0, target.selectedPosition);

    spinner2.clearSelection();
    assertEquals(0, target.selectedPosition);

    spinner3.performSelection(1);
    assertEquals(0, target.selectedPosition);

    spinner3.clearSelection();
    assertEquals(-1, target.selectedPosition);

    spinner1.performSelection(1);
    unbinder.unbind();
    spinner1.performSelection(0);
    assertEquals(1, target.selectedPosition);
    spinner2.performSelection(0);
    assertEquals(1, target.selectedPosition);
    spinner3.performSelection(0);
    assertEquals(1, target.selectedPosition);
    spinner1.clearSelection();
    assertEquals(1, target.selectedPosition);
    spinner2.clearSelection();
    assertEquals(1, target.selectedPosition);
    spinner3.clearSelection();
    assertEquals(1, target.selectedPosition);
  }

  static final class OptionalId {
    int selectedPosition = -1;

    @Optional @OnItemSelected(1) void select(int position) {
      selectedPosition = position;
    }

    @Optional @OnItemSelected(value = 1, callback = NOTHING_SELECTED) void clear() {
      selectedPosition = -1;
    }
  }

  @UiThreadTest
  @Test public void optionalIdPresent() {
    View tree = ViewTree.create(TestSpinner.class, 1);
    TestSpinner spinner = tree.findViewById(1);

    OptionalId target = new OptionalId();
    Unbinder unbinder = ButterKnife.bind(target, tree);
    assertEquals(-1, target.selectedPosition);

    spinner.performSelection(0);
    assertEquals(0, target.selectedPosition);

    spinner.clearSelection();
    assertEquals(-1, target.selectedPosition);

    spinner.performSelection(1);
    unbinder.unbind();
    spinner.performSelection(0);
    assertEquals(1, target.selectedPosition);
    spinner.clearSelection();
    assertEquals(1, target.selectedPosition);
  }

  @UiThreadTest
  @Test public void optionalIdAbsent() {
    View tree = ViewTree.create(TestSpinner.class, 2);
    TestSpinner spinner = tree.findViewById(2);

    OptionalId target = new OptionalId();
    target.selectedPosition = 1;
    Unbinder unbinder = ButterKnife.bind(target, tree);
    assertEquals(1, target.selectedPosition);

    spinner.performSelection(0);
    assertEquals(1, target.selectedPosition);

    spinner.clearSelection();
    assertEquals(1, target.selectedPosition);

    unbinder.unbind();
    spinner.performSelection(1);
    assertEquals(1, target.selectedPosition);
    spinner.clearSelection();
    assertEquals(1, target.selectedPosition);
  }

  static final class ArgumentCast {
    interface MyInterface {}

    View last;

    @OnItemSelected(1) void selectAdapterView(AdapterView<?> view) {
      last = view;
    }

    @OnItemSelected(2) void selectAbsSpinner(AbsSpinner view) {
      last = view;
    }

    @OnItemSelected(3) void selectMyInterface(MyInterface view) {
      last = (View) view;
    }

    @OnItemSelected(value = 1, callback = NOTHING_SELECTED)
    void clearAdapterView(AdapterView<?> view) {
      last = view;
    }

    @OnItemSelected(value = 2, callback = NOTHING_SELECTED)
    void clearAbsSpinner(AbsSpinner view) {
      last = view;
    }

    @OnItemSelected(value = 3, callback = NOTHING_SELECTED)
    void clearMyInterface(MyInterface view) {
      last = (View) view;
    }
  }

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

    Context context = InstrumentationRegistry.getContext();
    TestSpinner spinner1 = new MySpinner(context);
    spinner1.setId(1);
    TestSpinner spinner2 = new MySpinner(context);
    spinner2.setId(2);
    TestSpinner spinner3 = new MySpinner(context);
    spinner3.setId(3);
    ViewGroup tree = new FrameLayout(context);
    tree.addView(spinner1);
    tree.addView(spinner2);
    tree.addView(spinner3);

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

    spinner1.performSelection(0);
    assertSame(spinner1, target.last);

    spinner2.performSelection(0);
    assertSame(spinner2, target.last);

    spinner3.performSelection(0);
    assertSame(spinner3, target.last);

    spinner1.clearSelection();
    assertSame(spinner1, target.last);

    spinner2.clearSelection();
    assertSame(spinner2, target.last);

    spinner3.clearSelection();
    assertSame(spinner3, target.last);
  }
}