Back to Repositories

Testing OnItemLongClick Event Handling in ButterKnife

This integration test suite validates the OnItemLongClick functionality in ButterKnife, focusing on view binding and event handling for Android spinners. The tests verify various aspects of long-click event handling, including optional bindings, visibility modifiers, and argument casting.

Test Coverage Overview

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

Key areas tested include:
  • Basic long-click event handling with return values
  • Void return type handlers
  • Multiple view ID bindings
  • Optional view bindings
  • Different visibility modifiers
  • Argument type casting scenarios

Implementation Analysis

The testing approach uses a custom TestSpinner implementation extending AbsSpinner for controlled testing environments. Tests leverage JUnit and AndroidX test frameworks, implementing @UiThreadTest for UI interaction verification.

The implementation demonstrates proper binding lifecycle management through Unbinder, ensuring cleanup and preventing memory leaks.

Technical Details

Testing tools and configuration:
  • JUnit framework for test organization
  • AndroidX test libraries for UI testing
  • Custom ViewTree helper for view hierarchy creation
  • InstrumentationRegistry for context management
  • ButterKnife binding and unbinding utilities

Best Practices Demonstrated

The test suite exhibits several testing best practices:

  • Isolated test cases with clear setup and teardown
  • Comprehensive edge case coverage
  • Proper UI thread handling
  • Systematic verification of binding states
  • Clean separation of test scenarios
  • Thorough interface contract testing

jakewharton/butterknife

butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/OnItemLongClickTest.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.OnItemLongClick;
import butterknife.Optional;
import butterknife.Unbinder;
import com.example.butterknife.library.SimpleAdapter;
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 OnItemLongClickTest {
    static class TestSpinner extends AbsSpinner {
        public TestSpinner(Context context) {
            super(context);
            setAdapter(new SimpleAdapter(context));
        }

        boolean performItemLongClick(int position) {
            if (position >= 0) {
                AdapterView.OnItemLongClickListener listener = getOnItemLongClickListener();
                if (listener != null) {
                    return listener.onItemLongClick(this, null, position, NO_ID);
                }
            }

            return false;
        }
    }

    static final class Simple {
        boolean returnValue = true;
        int clickedPosition = -1;

        @OnItemLongClick(1) boolean itemClick(int position) {
            clickedPosition = position;
            return returnValue;
        }
    }

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

        assertTrue(spinner.performItemLongClick(0));
        assertEquals(0, target.clickedPosition);

        target.returnValue = false;
        assertFalse(spinner.performItemLongClick(1));
        assertEquals(1, target.clickedPosition);

        unbinder.unbind();
        spinner.performItemLongClick(2);
        assertEquals(1, target.clickedPosition);
    }

    static final class ReturnVoid {
        int clickedPosition = -1;

        @OnItemLongClick(1) void itemLongClick(int position) {
            clickedPosition = position;
        }
    }

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

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

        assertTrue(spinner.performItemLongClick(0));
        assertEquals(0, target.clickedPosition);

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

    static final class Visibilities {
        int clickedPosition = -1;

        @OnItemLongClick(1) public boolean publicItemLongClick(int position) {
            clickedPosition = position;
            return true;
        }

        @OnItemLongClick(2) boolean packageItemLongClick(int position) {
            clickedPosition = position;
            return true;
        }

        @OnItemLongClick(3) protected boolean protectedItemLongClick(int position) {
            clickedPosition = position;
            return true;
        }
    }

    @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.performItemLongClick(0);
        assertEquals(0, target.clickedPosition);

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

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

    static final class MultipleIds {
        int clickedPosition = -1;

        @OnItemLongClick({1, 2}) boolean itemLongClick(int position) {
            clickedPosition = position;
            return true;
        }
    }

    @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.performItemLongClick(0);
        assertEquals(0, target.clickedPosition);

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

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

    static final class OptionalId {
        int clickedPosition = -1;

        @Optional @OnItemLongClick(1) boolean itemLongClick(int position) {
            clickedPosition = position;
            return true;
        }
    }

    @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.performItemLongClick(0);
        assertEquals(0, target.clickedPosition);

        unbinder.unbind();
        spinner.performItemLongClick(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.performItemLongClick(0);
        assertEquals(-1, target.clickedPosition);

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

    static final class ArgumentCast {
        interface MyInterface {}

        View last;

        @OnItemLongClick(1) boolean itemLongClickAdapterView(AdapterView<?> view) {
            last = view;
            return true;
        }

        @OnItemLongClick(2) boolean itemLongClickAbsSpinner(AbsSpinner view) {
            last = view;
            return true;
        }

        @OnItemLongClick(3) boolean itemLongClickMyInterface(ArgumentCast.MyInterface view) {
            last = (View) view;
            return true;
        }
    }

    @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.performItemLongClick(0);
        assertSame(spinner1, target.last);

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

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