Skip to content

Stubs: Explained

haven1433 edited this page Nov 21, 2018 · 1 revision

What Are Stubs?

Without Stubs

Normally, an implementation of an abstract class or interface is created by making a new type that extends from that abstract class or interface. The writer then adds all the logic for the implementation directly into the new type.

For Example, StreamWriter implements (is a subclass of) TextWriter. TextWriter, as an abstract class, expects its subclass to provide certain members. The implementation for those members is contained directly in the StreamWriter class.

This is the usual way to provide an implementation for an abstract class or interface.

With Stubs

A Stub is a class that implements an interface or extends from an abstract class, but explicitly does not provide any implementing logic. Instead, it exposes public delegate members, so that implementations can be injected at runtime.

This layer of indirection, if used incorrectly, makes code more complex. However, using Stubs can be a lightweight alternative to creating custom types for one-off scenarios. This makes them useful for creating Null Objects, test doubles, and implementations of small, helper interfaces. In general, you use a Stub to replace a full object when the full object is not required or not desirable.

When to Use Stubs

Below are a few example scenarios where using Stubs can help make code cleaner.

Example 1: Do I really need a whole new type just for this little utility implementation?

Consider System.Windows.Input.ICommand. WPF uses commands regularly, and provides a special implementation of ICommand called RoutedCommand that it uses heavily. But despite this specialized implementation, most WPF types are designed to accept any implementation of ICommand.

One possible use of ICommand is to provide an implementation for a Button. When the Button is clicked, it can either call a Click event handler (usually implemented inside a UserControl or Window that owns the Button), or it can call the Execute method on an ICommand (usually part of a ViewModel, allowing for a better separation between the UI and the business logic). ICommand.CanExecute gets called to check if the Button should even be enabled, and the ICommand provides an event handler called CanExecuteChanged that the Button will automatically listen to in order to determine when to check CanExecute again.

Overall, this is all very well designed. However, if your application needs many commands, you'll need to create a new implementation of ICommand for every command you need. This can cause an explosion of types in your assembly, or worse, it can cause extremely complex dependency scenarios if several of your commands use the same data.

At some point, you may decide to make a reusable Command, one that accepts delegates in the constructor or as properties, and then calls those delegates as implementations for CanExecute and Execute. It probably also has a method called RaiseCanExecuteChanged to allow the owner of the object to raise the event when needed. Maybe you name this class DelegateCommand or RelayCommand. Searching the internet will give you exactly these names, along with implementations from other people who felt this was useful enough to share.

Congratulations! You've just created a Stub. IDisposable and IComparer<> are other common .Net interfaces where developers commonly want a Stub in real code. You likely have small interfaces of your own, or small interfaces in common frameworks or tools that you use, and having automatic Stubs for these can help keep related code close together.

Example 2: Working with null is Hard

The newer version of C# include the ?. operator, useful for simplifying code where the object you're using may be null. However, how much simpler could your code be if you just never had to deal with null? Tony Hoare, the inventor of ALGOL W, called the null reference his 'billion dollar mistake.'

Here's code written before C#6:

return source != null ? source.CalculateResult() : -1;

Here's code written using C#6:

return source?.CalculateResult() ?? -1;

You probably think this looks much better. But wait, what if we had a special version of source available that encoded that extra bit of information about how the application should behave if there is no source object? This is usually called a NullObject, and it makes your code look like this:

return source.CalculateResult();

This is clearly the cleanest of the options, especially if there are other places where you have to do similar operations of conditionally returning the -1. You could easily write a custom type that provides this null implementation and name it something like NullSource. But given the simplicity of the logic, you could also use a stub. Below is the setup for a stub object that can act as a NullObject in this case:

source = new StubSource
{
   CalculateResult = () => -1
};

In this way, you can cross "make null implementation" off your todo list as well: the Stub implementation functions out-of-the-box by returning default values from every method and property, and can be quickly adjusted to fit whatever custom scenario you need.

Example 3: Testing

You may have heard of Moq, or NSubstitute, or FakeItEasy. Each of these frameworks exist to assist in the development of Unit Tests by allowing you to easily replace real dependencies with fake implementations, allowing you to test specific units of code in isolation, independent of the behavior of the rest of your application. These can be very helpful, and have a number of features that make writing tests easier. Unfortunately, they have some downsides:

  • Some frameworks require extension methods on basically everything, which can greatly reduce the helpfulness of Intellisense when writing unit tests.
  • Setting up a custom implementation can be quite verbose.
  • Use of dynamic proxies make them unsuitable for use in non-testing scenarios.

The Stubs created by AutoImplement have less testing specific features, but using Stubs for both your production code and unit tests can help unify your codebase and help all your code be more similar.

Clone this wiki locally