Hacker News new | ask | show | jobs
by WorldMaker 1163 days ago
> you can't generally mock a function

With `dynamic` and `DynamicObject`/`ExpandoObject` proxies (or even lower level System.Dynamic/System.Linq.Expression fun) you can mock anything you want to in C#.

Those tools go all the way back to the early days of Linq (and useful but somewhat broken DLR visions like IronPython). If you need to time travel even further back in the .NET stack, or if you are just allergic to/deathly afraid of the DLR as some people seem to be, System.Reflection.Emit has been there since day 1. It's awful to work with and even worse low-level experience than the DLR, but it is capable of a lot of things. If you've got an up-to-date compiler you can go the other direction and use the recent Source Generators to do all the same low-level things but this time in the context of Roslyn and at build/compile-time.

Obviously, that doesn't necessarily make it a good idea that just because you can do such things that you should do such things, but C# has far more powerful raw tools at its disposal than many people realize.

A lot of the boilerplate in DDD styles is simply a preference for it and (over-)design patterns as comfort food.

It's a further aside, but hand-written "Fakes" patterns require more up-front work but often seem to me much better than automated Mocks. I've never seen a good DDD pattern focus on good "Fakes", though, and sometimes I find DDD complexity gets in the way of good "Fakes".

1 comments

I'm on of those people that had no idea about all this extra functionality- thanks for opening that door. Do you have any resources on the hand writting "Fakes" patterns?
I don't have any resources directly off-hand, but the basic concept is implementing "shareable across multiple tests" versions of your dependencies that implement things relatively similar to the end product but in a way that uses fewer resources during testing and is hopefully more reproducible/unlikely to encounter transient environment bugs. (Though still overall more "fake" than "real", otherwise you are just building artisanal integration test harnesses.) Things like using in-memory or SQLite data stores instead of your production database type. Ideas like true secondary, simplified implementations of your abstractions. (There's no reason to have an interface that is only ever implemented by one class, so at least this is one reason to have a second implementation that fakes doing something useful.) In some ways I feel "the fakes pattern" really just means "the old way of writing tests before auto-mocking frameworks became popular", but testing patterns love to have names that change every couple of years.

There are obviously good reasons auto-mocking frameworks became popular as it can be too easy to fall into performance traps or to try to maintain two separate dependency stacks (and get dangerously close to all of your units tests as just baroquely complex integration tests), one of which may easily get out of date/diverge and is extremely fragile, especially if you don't have good abstractions up front. It's too easy for how easy you can build your "fake" data sources to accidentally create a lower common denominator of what you can safely test, either limiting the types of queries that you feel like you can add to production code (forcing you to avoid things that your production DB supports, but an SQLite or In Memory storage can't easily fake) or create growing missing coverage boundaries between "testable" and "production" code.

On the flip side though, the benefits of hand-written fakes should be that you better prove out your abstractions and how they are factored (if it is too hard to manually fake a dependency, then maybe that becomes a sign that the dependency needs to be refactored and/or a better abstraction found for it), and the tests overall more resemble your production code and how it operates in the wild. (Versus how I feel excessively mocked code starts to resemble "stage plays" that don't necessarily approach or model real world usage and behavior and it often remains too easy to "stage play" even when your abstractions are wrong/not helping you enough.)