Back to Repositories

Testing OnItemClick Event Binding Integration in ButterKnife

This integration test suite validates the OnItemClick functionality in ButterKnife’s view binding library, focusing on item click event handling for Android spinner components. The tests verify various binding scenarios including simple clicks, multiple bindings, visibility modifiers, and argument casting.

Test Coverage Overview

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

  • Simple item click handling and position tracking
  • Multiple bindings to single view
  • Different visibility modifier scenarios
  • Multiple ID bindings
  • Optional binding behavior
  • Argument type casting verification

Implementation Analysis

The testing approach utilizes JUnit and AndroidX test framework features, implementing UI thread testing with @UiThreadTest annotations. The tests employ a custom TestSpinner implementation to simulate item click events and verify binding behavior through position tracking and view reference validation.

Technical Details

Key technical components include:

  • AndroidX Test Instrumentation
  • JUnit 4 testing framework
  • Custom TestSpinner extending AbsSpinner
  • ButterKnife view binding
  • UI thread test synchronization
  • Mock adapter implementation

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases for specific functionality
  • Proper cleanup with unbinder
  • Edge case handling for optional bindings
  • Type safety verification
  • Comprehensive visibility modifier testing
  • Clear test method naming conventions

jakewharton/butterknife

butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/OnItemClickTest.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.OnItemClick;
import butterknife.Optional;
import butterknife.Unbinder;
import com.example.butterknife.BuildConfig;
import com.example.butterknife.library.SimpleAdapter;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assume.assumeFalse;

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

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

            AdapterView.OnItemClickListener listener = getOnItemClickListener();
            if (listener != null) {
                listener.onItemClick(this, null, position, NO_ID);
            }
        }
    }

    static final class Simple {
        int clickedPosition = -1;

        @OnItemClick(1) void itemClick(int position) {
            clickedPosition = position;
        }
    }

    @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.clickedPosition);

        spinner.performItemClick(0);
        assertEquals(0, target.clickedPosition);

        unbinder.unbind();
        spinner.performItemClick(1);
        assertEquals(0, target.clickedPosition);
    }


    static final class MultipleBindings {
        int clickedPosition1 = -1;
        int clickedPosition2 = -1;

        @OnItemClick(1) void itemClick1(int position) {
            clickedPosition1 = position;
        }

        @OnItemClick(1) void itemClick2(int position) {
            clickedPosition2 = position;
        }
    }

    @UiThreadTest
    @Test public void multipleBindings() {
        assumeFalse("Not implemented", BuildConfig.FLAVOR.equals("reflect")); // TODO

        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.clickedPosition1);
        assertEquals(-1, target.clickedPosition2);

        spinner.performItemClick(0);
        assertEquals(0, target.clickedPosition1);
        assertEquals(0, target.clickedPosition2);

        unbinder.unbind();
        spinner.performItemClick(1);
        assertEquals(0, target.clickedPosition1);
        assertEquals(0, target.clickedPosition2);
    }


    static final class Visibilities {
        int clickedPosition = -1;

        @OnItemClick(1) public void publicItemClick(int position) {
            clickedPosition = position;
        }

        @OnItemClick(2) void packageItemClick(int position) {
            clickedPosition = position;
        }

        @OnItemClick(3) protected void protectedItemClick(int position) {
            clickedPosition = position;
        }
    }

    @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.clickedPosition);

        spinner1.performItemClick(0);
        assertEquals(0, target.clickedPosition);

        spinner2.performItemClick(1);
        assertEquals(1, target.clickedPosition);

        spinner3.performItemClick(2);
        assertEquals(2, target.clickedPosition);
    }

    static final class MultipleIds {
        int clickedPosition = -1;

        @OnItemClick({1, 2}) void itemClick(int position) {
            clickedPosition = position;
        }
    }

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

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

        spinner1.performItemClick(0);
        assertEquals(0, target.clickedPosition);

        spinner2.performItemClick(1);
        assertEquals(1, target.clickedPosition);

        unbinder.unbind();
        spinner1.performItemClick(2);
        assertEquals(1, target.clickedPosition);
        spinner2.performItemClick(2);
        assertEquals(1, target.clickedPosition);
    }

    static final class OptionalId {
        int clickedPosition = -1;

        @Optional @OnItemClick(1) void itemClick(int position) {
            clickedPosition = position;
        }
    }

    @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.clickedPosition);

        spinner.performItemClick(0);
        assertEquals(0, target.clickedPosition);

        unbinder.unbind();
        spinner.performItemClick(1);
        assertEquals(0, target.clickedPosition);
    }

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

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

        spinner.performItemClick(0);
        assertEquals(-1, target.clickedPosition);

        unbinder.unbind();
        spinner.performItemClick(0);
        assertEquals(-1, target.clickedPosition);
    }

    static final class ArgumentCast {
        interface MyInterface {}

        View last;

        @OnItemClick(1) void itemClickAdapterView(AdapterView<?> view) {
            last = view;
        }

        @OnItemClick(2) void itemClickAbsSpinner(AbsSpinner view) {
            last = view;
        }

        @OnItemClick(3) void itemClickMyInterface(ArgumentCast.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.performItemClick(0);
        assertSame(spinner1, target.last);

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

        spinner3.performItemClick(0);
        assertSame(spinner3, target.last);
    }
}