2

I want to unit test an extension method for a type WebSiteResource, where this type WebSiteResource is an implementation of an abstract type. I need to set or mock a value to a property, but it's not virtual.

public static string HostName(this WebSiteResource webSite) => webSite.Data.DefaultHostName;
  • I can't mock WebSiteResource, because it's not an interface
  • I can't instantiate WebSiteResource because property Data requires complex setup
  • I can't create a MockableWebSiteResource that inherits from WebSiteResource (and doing something like public new virtual Data { get; set; }) because Data requires complex setup

How do you design methods to avoid making them non-testable?

Update (after answer):

What I in fact have is a non-trivial method like

public static bool IsValid(this WebSiteResource webSite) {
   // and then complex logic for multiple properties etc
}
Kristoffer Jälén
  • 4,112
  • 3
  • 30
  • 54
  • 1
    Your `HostName` method is so trivial, why do you feel you should write a unit-test for it? – Dai May 11 '22 at 10:01
  • 1
    You could try to create a wrapper class to encapsulate WebSiteResource and an interface for wrapper. Than you could mock and inject it easily. – Aleksej Vasinov May 11 '22 at 10:22
  • 1
    @AleksejVasinov It's wrappers all the way down... – Dai May 11 '22 at 10:23
  • 1
    w.r.t your `bool IsValid` function... unfortunately there's no good solution for that. I do now understand and appreciate why you'd want to have tests for your logic in there - but I think that _even if_ you did painstakingly write all the wrappers and adapters to accomplish it, you'd only end-up duplicating application logic in your test-cases, making it entirely redundant. So I'd ask what the consequences-of-failure are: is this a safety-critical, high-reliability system? Or just some personal project? What do other people on your team think? _What's the harm in **not** having tests?_ – Dai May 11 '22 at 10:53

1 Answers1

3

I want to unit test an extension method...

The string HostName() method you posted is a trivial function: I don't think there is any value in writing any kind of test code for it.

Automated tests are great, but they introduce their own brittleness and maintenance burden. If it took you 3 minutes to research and write that function, it's now going to take you far more additional minutes to think-up all the different weird and exotic edge-cases where your HostName function will fail (e.g. website.Data is null under some obscure network situation or race-condition).


Disregarding this specific function, but in general: regarding how to design testable code which directly consumes objects from opaque external libraries... well, the simple answer is don't: (as in: don't write application code that directly consumes external library objects and directly passes those objects deeper into your own application code).

So instead set-up clear boundaries: software bulkheads between distinct components in your application code and never let a naked external object cross that line. If you need to pass some data from an external library across the line then introduce your own POCO data-transfer-object and copy the values into it, then pass that along.

The alternative path of least resistance only leads to worse outcomes in the long-run: eventually every project in your solution has to reference all the same NuGet packages and assembly references so external library objects can be passed around (such as by passing that WebSiteResource object all the way directly to your user-interface).

....so you'll run into more and more of the problems like you've illustrated: even though it's your project you no-longer have any real control over the opaque objects you're using: in order to accomplish some future goal you'll likely find yourself fighting those libs and needing to write hackish code to work-around them instead of just directly editing their source and recompiling them, because you don't control them.


Sidebar: Personally, I blame C#'s inflexible type-system...

I note that a large part of this problem is simply because of how C# and the .NET CLR works: that ultimately you're restricted from manipulating that WebSiteResource type (and its object-instances) by the .NET type-system (...at least in Java everything is virtual).

Because C#/.NET uses a nominative type-system that's strictly enforced by the runtime, and how it doesn't support structural-typing or true algebraic types it means we can't do things like class HahahImSubclassingYouAnyway extends WebSiteResource (or even class HahahImSubclassingYouAnyway implements WebSiteResource in some languages). At least we can use composition (which we should be doing anyway), but again, C#/.NET is still dragging its feet when it comes to making it easy to compose types together, not to mention how it's impossible to maintain reference identity (which is why we're forced to use inheritance-based subclassing in some situations).

Anyway, I'm ranting...


I can't mock WebSiteResource, because it's not an interface

  • Assuming you're still dead-set on this, then implement your own adapter types.
    • Yes, it's exactly as tedious and error-prone as it sounds.
    • Yes, this is why it's probably an inappropriate use of your time to work on this.

I can't instantiate WebSiteResource because property Data requires complex setup

The fact you wrote that makes it sound like you're thinking of writing test-cases that will essentially test the WebSiteResource object far more than your own code. I know that's not your intention, though.

I can't create a MockableWebSiteResource that inherits from WebSiteResource (and doing something like public new virtual Data { get; set; }) because Data requires complex setup

Whoa, stop, hold it right there...

What you're describing is an abuse of OOP inheritance. Your proposed MockableWebSiteResource clearly shouldn't have an "is" conceptual relationship with WebSiteResource, but that's exactly what OOP class inheritance represents (and WebSiteResource represents a fully-fleshed out service implementation, not an abstract service). This is a sign that something isn't right with your approach. Stop. Think. Reconsider.

(And because Java-style OOP inheritance (which C# shares) is so limiting and inelegant it just ends-up creating problems instead of solving them: it's just bad. I'm sure you've run into situations where you found it impossible to use OOP inheritance for any kind of direct domain modelling).

(With apologies to Smalltalk and Simula fans (we love you), but I'm using the term "OOP" in the Java-family sense: so I am not referring to the broader (or purer) higher-minded concepts of O.G. OOP where message-passing and delegation are used instead of virtual methods and inheritance)

How do you design methods to avoid making them non-testable?

I can't give you anything other than general guidelines that we've all head before:

Dai
  • 141,631
  • 28
  • 261
  • 374
  • Thanks, my example `HostName` was maybe not good. What I in fact have is a method like `public static bool IsValid(this WebSiteResource webSite) { // and then complex logic for multiple properties ec }`. But I get your point about setting up clear boundaries. Good explanation! – Kristoffer Jälén May 11 '22 at 10:40
  • @KristofferJälén I hope by "composition" you don't mean "wrappers of wrappers" and [maybe some Enterprise-y design-patterns thrown in for good measure](https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition). – Dai May 11 '22 at 10:56
  • Oh, it becomes clearer.. I guess I can't unit test the `IsValid` method, just as you write in [your comment](https://stackoverflow.com/questions/72199008/how-do-you-design-methods-to-avoid-making-them-non-testable#comment127563300_72199008). – Kristoffer Jälén May 11 '22 at 11:48