Should I test that?

Should I test interfaces?

Verdict

No

Do not write tests for an interface that only declares methods; test each class that implements it, with one shared contract test when several classes implement the same interface.

Why

Do not write tests for an interface that only declares methods; test the classes that implement it. For the typical case, a UserStore interface in Java, C# or TypeScript with one or two implementations, Blast radius is users because the implementations serve user features, and Change frequency is rarely because the interface changes a few times a year. Detectability is immediately: the compiler rejects a class that misses or mistypes a declared method. Reversibility is trivial because a revert undoes a wrong declaration, and Test cost is trivial, so rule R8 gives Do not test.

When the decision changes
WhenDecisionWhy
An interface method has a default body with logic, such as formatting a display nameTest the default method minimally, through a small implementing class in the testDetectability moves to same-day: a wrong default body breaks every implementation at once
A default method on the interface calculates the tax on an order totalTest mandatory, including rounding and boundary amountsBlast radius rises to money, Reversibility to costly and Detectability to eventually
Tests use an in-memory implementation of a repository interface, and production uses the Postgres oneTest: one contract suite that runs against both implementationsChange frequency rises to regularly, Detectability to eventually, Reversibility to with-effort and Test cost to moderate: the fake drifts from the real store
The interface ships in a library, and other teams write the implementationsTest minimally: publish a contract test kit that each implementing team runsDetectability moves to eventually, Reversibility to with-effort and Test cost to moderate: other teams find the breakage after their release
Spring Data generates the implementation of a repository interface from its query method namesTest: one database test per declared query methodChange frequency rises to regularly, Detectability to eventually, Reversibility to with-effort and Test cost to moderate: a query method named with Or in place of And returns plausible wrong rows, and code that updates or emails those rows leaves records to repair

What breaks if you don't test

An interface without tests of its own breaks nothing that the compiler misses, because a declaration has no body. The failures that reach users sit in the implementations. A PostgresUserStore that compares emails case-sensitively misses a user who signed up as ana@example.com and now types ANA@example.com, so the signup form creates a second account. Support notices weeks later, when she asks where her order history went. A test of PostgresUserStore finds that bug; a test of the UserStore declaration cannot.

What you lose if you over-test

The common over-test uses reflection to assert that UserStore declares save and findByEmail with the right parameter types. The compiler already checks those signatures in every implementation and caller, so the test catches nothing that the build misses, and each signature change needs a second edit in the test. Another over-test mocks the interface and asserts that the mock returns its stubbed value. That test checks the mocking library and none of your code, yet it raises the coverage number.

What to do instead

Test each implementation through the interface type: unit tests for classes that work in memory, integration tests for classes that talk to a database. When several classes implement one interface, write the shared expectations once as a contract test and run it against each class. In JUnit 5 and later, declare the tests as default methods in a test interface, as the JUnit guide to test interfaces shows. In pytest, a parametrised fixture runs every test once per implementation.

When the answer changes

  • An interface method gains a default body with logic, such as a formatting rule or a tax calculation.
  • A second implementation appears, and the test suite relies on one class while production runs the other.
  • A framework generates the implementation from the interface, as Spring Data does for repository query methods.

Code example

One contract for two stores

Every UserStore must pass the contract below. InMemoryUserStore serves the fast service tests, and PostgresUserStore runs in production:

interface UserStoreContract {
    UserStore createStore();

    @Test
    default void findsSavedUserByEmailIgnoringCase() {
        UserStore store = createStore();
        store.save(new User("ana@example.com"));
        assertTrue(store.findByEmail("ANA@example.com").isPresent());
    }

    @Test
    default void returnsEmptyForUnknownEmail() {
        assertTrue(createStore().findByEmail("bob@example.com").isEmpty());
    }
}

class InMemoryUserStoreTest implements UserStoreContract {
    public UserStore createStore() { return new InMemoryUserStore(); }
}

class PostgresUserStoreTest implements UserStoreContract {
    public UserStore createStore() { return new PostgresUserStore(TestDatabase.dataSource()); }
}

Each test class supplies only a store. If the Postgres class matches emails case-sensitively, or the in-memory class compares them with equals(), findsSavedUserByEmailIgnoringCase fails for that class in the next CI run. Without the contract, the service tests pass against a fake that disagrees with production.

FAQ

Do you unit test interfaces?

No, an interface that only declares methods has no code that can fail, so it needs no unit test of its own. Unit test each class that implements it, through the interface type.

Do I need to test each implementation of an interface?

Yes, each implementation holds its own code and needs its own tests. Write the shared expectations once as a contract test, run it against every implementation, and add tests for what each class does beyond the contract.

Should I test default methods in Java interfaces?

Yes, a Java default method has a body, so test it with one or two tests of the main path. Declare a minimal class in the test that implements the abstract methods with fixed values, then call the default method and assert on the result.

Should I test Spring Data repository interfaces?

Yes, test each query method that you declare with one test against a database, because Spring Data generates its query from the method name. A method named findByStatusOrEmail where findByStatusAndEmail was meant starts without errors and returns plausible wrong rows, and code that updates or emails those rows leaves records to repair. Inherited methods such as save and findById need no tests of yours.