Interface Overloading - TDD Against UI
WTF...
... is interface overloading. Or Overriding. I kinda toggle between the terms. Right now I'm favoring Interface Overloading; so that's what the title is.
Interface Overloading
Interface overloading is the process I've found in C#
and JAVA
and Swift
to allow TDD when using Libraries and OS Systems. I figure it'll work in most (if not all) languages with interfaces. Except GoLang; this is kinda how GoLang works.
It's a mechanism I stumbled across while TDDing up an android app.
I wrote this tdd-against-android-widgets post while I first found it. It was part of a long and twisted path... Which I think resulted in an excellent tool.
Simple Definition
Extending a class and implementing an interface of a base class method allowing passing around as an interface.
The driving forces behind this discovery is TDD, Extreme Encapsulation, and Clean Architecture.
Mix-In
The basic idea really comes into a mix-in style that gives functionality as required to classes you don't control.
Where I discovered this, and I'm confident it will have significant impact is the UI. It can be utilized against Library and OS classes as easily, but I haven't encountered any cases yet that scream "WIN" as loudly as UI does.
Example
Let's go for a simple example that was TDD'd. The code link is available at the end of the post.
The base class
public class BaseClass
{
public string TheMethod(int intVal, bool boolVal) => $"StringVal {intVal} {boolVal}";
}
A simple example with just one method. TheMethod
. It builds a string and returns it.
TheMethod
is not a virtual method so it's unable to be mocked.
We need a way to pass this around. Really; this example is overly simplified. A MAJOR reason it works so well for UI, is that those often tie themselves to the UI thread, which makes calling textBox.Text = "example";
in a unit test will throw an exception. In Android, it's due to the OS not actually being there.
For C#, I'm working in UWP; and it's a "Ui Changed on non-ui thread" exception.
I suspect very similar if working in WinForms. In general; the UI is thread restricted which forces what I've seen from some frameworks; the ability to force a specific thread.
Now that we have a method we want to use in a method; we need to create the interface to hook into it.
public interface ITheInterface {
string TheMethod(int intVal, bool boolVal);
}
Yep; same signature. Not a lot to go over here. It's an interface with the same definition... yeah.
OK; next up!
public class WrapperClass : BaseClass, ITheInterface { }
And here we have our Wrapper
class. It's the object we'll use in our UI.
In android it'll look like
<com.quantityandconversion.widget.WrapperClass
android:id="@+id/tv_score_value"
android:layout_width="match_parent"
android:layout_height="match_parent" />
that can be seen here
and in XAML it'll look like
<userControls:WrapperClass x:Name="TxtTitle" Canvas.Top="1" Canvas.Left="1" HorizontalAlignment="Left" HorizontalContentAlignment="Left"/>
and can be seen here
The base class of these are have been TextBox
in actual implementation; and did the Interface Overload for setting text.
Both of the above repo's can be explored to see how I've done used it.
Use this WrapperClass
control
Using Android; we'll look at the TopItemsAdapter
from my HackerNews Reader experiment project.
We retrieve the object like so in our TopItemsAdapter .ViewHolder
/* package */ static class ViewHolder extends RecyclerView.ViewHolder{
private QacTextView points;
private QacTextView comments;
private QacTextView time;
...
/* package */ ViewHolder(final View itemView) {
super(itemView);
...
points = (QacTextView)itemView.findViewById(R.id.tv_score_value);
comments = (QacTextView)itemView.findViewById(R.id.tv_comments);
time = (QacTextView)itemView.findViewById(R.id.tv_posted_time);
}
}
And we'll use these control in the Item
class; which I won't reproduce the entirety of here; just some relevant methods
public class Item {
...
public void postTimeInto(final SetText item) {
postTime.postTimeInto(item);
}
public void commentCountInto(final SetText item) {
itemComments.commentCountInto(item);
}
public void scoreInto(final SetText item) {
itemScore.scoreInto(item);
}
...
}
We can see the use of the SetText
interface in these methods.
The way this is used by the `TopItemsAdapter' is by passing the correct control into each method
@Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
final Item story = topItemsActivityMediator.itemAt(position);
...
story.commentCountInto(viewHolder.comments);
story.scoreInto(viewHolder.points);
story.postTimeInto(viewHolder.time);
...
}
and with this pattern in place; you maintain encapsulation, and can still produce the data to be displayed.
Examples
The available simple examples are available in my InterfaceOverride git repo.
Summary
This isn't a complex way to abstract classes you don't control. It's just a really useful one... at least very useful in the UI... for me. I hope it can help others TDD more of the application. Other tricks may be required for other things; but that's a post for another day.
This was a really quick write up; happy to improve and answer questions about the process.
UPDATE
This is a major component in my Hotel Pattern to maintain Clean Architecture.