A Small Decision That Determines Whether Your Code Can Be Tested: Interface or Concrete Class in DI Registration

Lifetime selection Singleton, Scoped, or Transient usually receives the most attention when examining Dependency Injection configurations. Alongside it comes a less noticeable but no less significant detail: whether the registration registers a concrete class independently or translates an interface to an implementation. Although this decision seldom results in an obvious flaw, it may have a big impact on how simple it is to isolate and test a class.

Two Registrations, One Subtle Difference

Both compile cleanly. Both inject successfully. Both work correctly for a real user hitting a real endpoint. The difference becomes apparent when someone tries to test the class in isolation, particularly when the implementation has infrastructure dependencies such as a live database.

How the Controller Differs in Each Case

The first registration style typically leads to a Controller written like this:

The second leads here instead:

One word changes in two places, but that single word can have a significant effect on how easily the dependency can be substituted during testing.

Why the Interface Version Makes Testing Easier

Unit testing a Controller without touching a real database requires substituting a fake or mock implementation in place of the real dependency. When the Controller depends on an interface, that substitution is explicit:

With IclsWorkFlow as the dependency, a test can supply FakeWorkFlow instead of the real, Oracle-backed clsWorkFlow:

The Controller depends only on the contract defined by IclsWorkFlow. The test can therefore provide another implementation of that contract without changing the Controller itself.

If the Controller depends directly on the concrete clsWorkFlow class, substitution is less straightforward. The constructor requires that concrete type, so a test cannot simply provide an unrelated fake implementation. Depending on the design, testing may instead require a testable subclass, virtual members, a mocking framework that supports the concrete type, or other techniques.

For this reason, depending on an interface is generally a cleaner approach when a dependency needs to be isolated during unit testing.

Why This Gets Overlooked

Registering a concrete class directly is often the simplest path early in a project. It compiles without warnings, and the missing interface causes no immediate problem.

The cost surfaces later — when a test is finally needed and the Controller is tightly coupled to a concrete implementation with infrastructure dependencies. Retrofitting an interface at that point means touching registrations and constructors already depending on the concrete type, which can be a larger undertaking than defining the abstraction correctly from the outset.

This is why interface-based registration is often treated as a useful default in ASP.NET Core projects, particularly for services that represent application boundaries or infrastructure dependencies.

Defining an interface costs little upfront — a handful of method signatures — but preserves the option to provide alternative implementations for testing or other scenarios without changing the consuming class.

Interface Registration and Lifetime Are Separate Decisions

It is also important not to confuse the interface-versus-concrete choice with the service lifetime.

For example:

contains two separate decisions:

  • IclsWorkFlow defines the abstraction consumed by other classes.
  • Scoped defines how the implementation instance is managed by the DI container.

An interface does not automatically make a service Scoped, and a concrete registration does not automatically make a service Singleton or Transient.

The abstraction and the lifetime should each be selected based on the design requirements of the service.

A Habit Worth Adding to Registration Review

When reviewing a service registration, it is worth checking two things at once: which lifetime was selected, and whether the registration binds an interface to an implementation or simply registers a concrete type.

For any class plausibly needing isolation in tests later — particularly Data Access and infrastructure-related classes — defining and registering against an interface from the start can make testing and substitution considerably easier.

This does not mean every concrete class must have an interface. Small internal classes, value-like components, or implementations that do not need substitution may not benefit from an additional abstraction.

The important question is whether the consuming code should depend on a concrete implementation or on a contract that can have multiple implementations.

Takeaway

The difference between services.AddTransient<clsWorkFlow>() and services.AddScoped<IclsWorkFlow, clsWorkFlow>() may look small, but it can significantly affect the testability and flexibility of the consuming code.

The interface-based registration allows the Controller to depend on a contract rather than a specific implementation, making it easier to substitute a fake or mock dependency during unit testing. Concrete classes can also be tested, but substitution may require additional techniques depending on the class design.

The registration therefore deserves attention alongside the lifetime decision. Choosing an appropriate abstraction at the DI boundary can help keep application code easier to test, maintain, and evolve.

Summary

Dependency Injection registration involves more than selecting Singleton, Scoped, or Transient. Choosing whether a consumer depends on an interface or a concrete implementation also affects testability and maintainability. Interface-based registration makes dependency substitution explicit and allows tests to provide alternative implementations without changing the consuming class. Concrete classes can still be tested, but replacing them may require additional techniques. The appropriate choice depends on whether the dependency benefits from abstraction, substitution, or isolation during testing.

Recommendation for ASP.NET 11.0 Hosting

A solid base for developing online services and applications is ASP.NET Core 11. Before creating an ASP.NET web application, you must be proficient in JavaScript, HTML, CSS, and C#. There are thousands of web hosting providers offering ASP.NET hosting on the market. However, there are relatively few web hosting providers that offer top-notch ASP.NET hosting.

ASP.NET is the best development language in Windows platform, which is released by Microsoft and widely used to build all types of dynamic Web sites and XML Web services. With this article, we’re going to help you to find the best ASP.NET Hosting solution in Europe based on reliability, features, price, performance and technical support. After we reviewed about 30+ ASP.NET hosting providers in Europe, our Best ASP.NET Hosting Award in Europe goes to HostForLIFE.eu, one of the fastest growing private companies and one of the most reliable hosting providers in Europe.

You may also like...

Popular Posts

Skip to toolbar Log Out