Unmockable 3.0.132

dotnet add package Unmockable --version 3.0.132
NuGet\Install-Package Unmockable -Version 3.0.132
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Unmockable" Version="3.0.132" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Unmockable --version 3.0.132
#r "nuget: Unmockable, 3.0.132"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Unmockable as a Cake Addin
#addin nuget:?package=Unmockable&version=3.0.132

// Install Unmockable as a Cake Tool
#tool nuget:?package=Unmockable&version=3.0.132

build status codecov maintainability nuget stryker

📢 Shout-out

A big shoutout to Microsoft and other vendors to start unit testing your SDKs so you'll share our pain and give us some freaking extension points.

Dependency Inversion Principle One should "depend upon abstractions, not on concretions."

Please, don't give us the Unmockable<🖕>.

Support

Please, retweet to support this petition and @mention your vendor.

Unmockable

Imagine you need a dependency on a 3rd party SDK where all types carry no interfaces, and all methods are not virtual. Your only option is writing a wrapper that either implements an interface or has its methods marked virtual and does nothing more than passing through calls to the underlying object.

That's where this tiny library comes in. It acts as that handwritten wrapper for you.

Not a replacement

For dependencies under control, introduce interfaces, and use a regular mocking framework like NSubstitute or Moq. Kindly send an email to the vendor of the SDK you're using if they could pretty please introduce some interfaces. It is a no-brainer to extract an interface, and it helps us to be SOLID.

Feature slim

This library has a particular purpose and is deliberately not a full-fledged mocking framework. Therefore I try to keep its features as slim as possible, meaning:

  • All mocks are strict; each invocation requires explicit setup.
  • <s>There are are no</s> wild card argument matchers.
  • The API is straightforward and sparse.
  • Wrapping static classes is not supported.

That being said, I genuinely believe in pure TDD, so everything is written from a red-green-refactor cycle, and refactoring is done with SOLID principles and Design Patterns in hand. If you spot a place where another pattern could be applied, don't hesitate to let me know.

Different

What makes it different from Microsoft Fakes, Smocks, or Pose is that it only uses C# language constructs. There is no runtime rewriting or reflection/emit under the hood. Of course, this impacts the way you wrap and use your dependency, but please, don't let us clean up someone else's 💩.

Usage

I prefer NSubstitute over Moq for its crisp API. However, since we are (already) dealing with Expressions, I felt it was more convenient (and easier for me to implement) to resemble the Moq API.

💉 Inject

Inject an unmockable* object:

public class SomeLogic
{
    public SomeLogic(IUnmockable<HttpClient> client)
    {
        _client = client;
    }
}

Wrap the execution of a method in an expression:

public async Task DoSomething(int input)
{
    await _client.Execute(x => x.DownloadAsync(...));
}

* The HttpClient is just a hand-picked example and not necessarily unmockable. There have been some debate around this type. Technically it is mockable, as long as you are not afraid of message handlers.

Concrete unmockable types (pun intented) I had to deal with recently are the ExtensionManagementHttpClient and the AzureServiceTokenProvider.

↪️ Intercept

Inject an interceptor from a test using Unmockable.Intercept:

var client = Interceptor.For<HttpClient>()
    .Setup(x => x.DownloadAsync(...))
    .Returns(...);

var target = new SomeLogic(client);
await target.DoSomething(3);

client.Verify();

Note: Since v3 the API has changed in Interceptor.For to generate an interceptor for some unmockable.

Only strict 'mocks' are supported, meaning all invocations require setup, and all setups demand invocation. Using strict mocks saves you from NullReferenceExceptions and makes verification easy.

If you really want a stub instead of a mock, I'd recommend auto-mocking with AutoFixture:

var fixture = new AutoFixture();
fixture
    .Customize(new AutoConfiguredNSubstituteCustomization());

var client = fixture
    .Create<IUnmockable<HttpClient>>();

var target = new SomeLogic(client);
await target.DoSomething(3);

🎁 Wrap

Inject the wrapper object using Unmockable.Wrap:

services
    .AddTransient<IUnmockable<HttpClient>, Wrap<HttpClient>>();
services
    .AddScoped<HttpClient>();

Or wrap an existing object:

var client = new HttpClient().Wrap();

Or add wrappers for all services with Unmockable.DependencyInjection:

services
    .AddScoped<HttpClient>();
services
    .AddUnmockables();

Remark: The expressions are compiled at runtime on every invocation, so there is a performance penalty. I tried to add caching here, but that turns out not to be a sinecure.

Matchers

Collection arguments get unwrapped when matching the actual call with provided setups! Value types, anonymous types and classes with a custom GetHashCode() & Equals() should be safe. You can do custom matching with Arg.Ignore<T>(), Arg.Where<T>(x => ...) and Arg.With<T>(x => ...), though the recommendation is to be explicit as possible.

matcher description
Ignore ignore the actual value
Where match the actual value using the predicate
With do something like an assertion on the actual value

Using explicit values in the setup:

Interceptor
    .For<SomeUnmockableObject>()
    .Setup(m => m.Foo(3))
    .Returns(5);

When the actual value doesn't matter or is hard or impossible to setup:

Interceptor
    .For<SomeUnmockableObject>()
    .Setup(m => m.Foo(Arg.Ignore<int>()))
    .Returns(5);

If you need some more complex matching:

Interceptor
    .For<SomeUnmockableObject>()
    .Setup(m => m.Foo(Arg.Where<int>(x => x > 5 && x <= 10)))
    .Returns(5);

Assertion on the arguments is done using the With matcher. This is a bit of a combination of Ignore and Where, since you receive the value in the lambda but do not provide a result for the matcher.

The assertion should throw an exception when the actual value does not meet your expectations.

Interceptor
    .For<SomeUnmockableObject>()
    .Setup(m => m.Foo(Arg.With<int>(x => x.Should().Be(3, ""))))
    .Returns(5);

Mind that you need to specify values for all optionals also since expression trees may not contain calls that uses optional arguments.

Optional arguments not allowed in expressions

An expression tree cannot contain a call or invocation that uses optional arguments

You can use the default literal for all arguments (in C# 7.1.), but be aware that this is the default value of the type, which is not necessarily the same as the default value specified for the argument!

client.Execute(x => x.InstallExtensionByNameAsync("some-value", "some-value", default, default, default));

On the plus side, you now have to make it explicit both on the Execute and the Setup end making it less error-prone.

Unmockable unmockables

What if your mocked unmockable object returns an unmockable object?! Just wrap the (in this case) data fetching functionality in a separate class, test it heavily using integration tests, and inject that dependency into your current system under test.

Static classes

At first, I added, but then I removed support for 'wrapping' static classes and invoking static methods. In the end, it is not an unmockable object! If you're dependent, let's say, on DateTime.Now you can add a method overload that accepts a specific DateTime. You don't need this framework for that.

public void DoSomething(DateTime now)
{
    if (now ...) {}
}

public void DoSomething() => DoSomething(DateTime.Now)

Or with a factory method if it has to be more dynamic.

public void DoSomething(Func<DateTime> now)
{
    while (now() <= ...) {}
}

public void DoSomething() => DoSomething(() => DateTime.Now)

If you don't like this change in your public API, you can extract an interface and only include the second method (which is a good idea anyway), or you mark the overloaded method internal and expose it to your test project with the [InternalsVisibleTo] attribute.


Happy coding!

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp1.0 was computed.  netcoreapp1.1 was computed.  netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard1.3 is compatible.  netstandard1.4 was computed.  netstandard1.5 was computed.  netstandard1.6 was computed.  netstandard2.0 was computed.  netstandard2.1 was computed. 
.NET Framework net46 was computed.  net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen30 was computed.  tizen40 was computed.  tizen60 was computed. 
Universal Windows Platform uap was computed.  uap10.0 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Unmockable:

Package Downloads
Unmockable.Wrap

Wrap unmockable objects.

Unmockable.Intercept

Intercept calls on wrappers around unmockable objects.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
3.0.132 1,759 4/19/2023
3.0.131 316 4/19/2023
3.0.120 6,233 2/25/2021
3.0.99 722 2/25/2021
3.0.93 800 12/18/2020
3.0.92 780 12/18/2020
3.0.91 1,096 11/2/2020
3.0.89 1,090 11/21/2019
3.0.88 866 11/21/2019
2.2.86 942 10/10/2019
2.2.85 934 10/10/2019
2.2.84 921 10/10/2019
2.1.80 916 10/9/2019
2.1.79 883 10/8/2019
2.0.77 861 10/8/2019
2.0.76 855 10/4/2019
2.0.75 879 10/4/2019
2.0.73 867 10/3/2019
2.0.72 885 10/3/2019
1.0.71 917 10/1/2019
0.0.70 899 9/24/2019
0.0.68 940 9/20/2019
0.0.66 911 9/5/2019
0.0.65 935 9/5/2019
0.0.64 974 8/29/2019
0.0.63 942 8/29/2019
0.0.62 922 8/12/2019
0.0.61 1,015 7/29/2019
0.0.59 943 7/29/2019
0.0.55 942 7/29/2019
0.0.51 940 7/29/2019
0.0.50 998 4/4/2019
0.0.49 965 3/22/2019
0.0.48 971 3/22/2019
0.0.47 994 3/20/2019
0.0.46 966 3/20/2019
0.0.45 974 3/19/2019
0.0.44 995 3/19/2019
0.0.43 960 3/19/2019
0.0.42 998 3/19/2019
0.0.41 931 3/19/2019
0.0.40 1,077 3/19/2019
0.0.39 950 3/15/2019
0.0.38 973 3/15/2019
0.0.37 972 3/14/2019
0.0.36 971 3/14/2019
0.0.35 980 3/14/2019
0.0.34 953 3/14/2019
0.0.33 857 3/14/2019
0.0.31 566 3/14/2019
0.0.30 557 3/14/2019
0.0.29 577 3/14/2019
0.0.28 568 3/12/2019
0.0.27 571 3/12/2019
0.0.26 554 3/12/2019
0.0.25 550 3/12/2019
0.0.24 559 3/12/2019
0.0.23 569 3/12/2019
0.0.22 561 3/12/2019
0.0.21 577 3/11/2019
0.0.20 564 3/11/2019
0.0.19 541 3/11/2019
0.0.18 578 3/9/2019
0.0.17 556 3/9/2019
0.0.16 552 3/9/2019
0.0.14 546 3/8/2019
0.0.13 568 3/8/2019
0.0.11 555 3/8/2019
0.0.10 557 3/8/2019
0.0.9 575 3/8/2019
0.0.8 585 3/8/2019
0.0.7 554 3/8/2019
0.0.5 568 3/8/2019

removed the wrapping of unmockables.