Marcin Jahn | Dev Notebook
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects

Unit Tests in C#

Test Doubles

When unit testing, we often have to provide fake implementations of the dependencies of the Service Under Test (SUT). These are called different names, but it’s good to actually differentiate between them and understand the differences between various kinds of test doubles.

  • Dummy - the simples possible implementation that is there just to provide it as an argument, probably to some constructor. Even a null could be considered as a dummy sometimes.
  • Stub - a slight step up from dummies, they implement methods/properties in the most straightforward way, most likely hardcoding return values. The difference from dummy is the fact that with dummies we mostly care about the implementation just being there, without the need of it actually working in any way.
  • Spy - a test double that is able to record actions done on it. This way we can validate if some dependency was ever called, or how many times it happened.
  • Fake - a stub with more complex implementation than a one-liner hardcoded return value. An example could be a fake that contains some in-memory dictionary that simulates a database.
  • Mock - an implementation produced by a mocking library, like Moq or NSubstitute. It can often behave like a dummy, stub, spy, or fake.

Approaches to Testing

When writing tests, it’s good to follow the below advice:

Use Stubs or Fakes with Queries, and Mocks with Commands.

Using Mocks (or Spies) to verify Queries is troublesome, since it introduces interaction-based approach to a test that could be writtn in a state-based manner.

State-based Testing

Preferably, if possible, it’s best to use state-based testing approach, since it does not rely that much on internal implementation of the tested thing. It makes tests less fragile. The tests are fragile when internal changes in the implementation require chanes in the tests, even though the public contract of the tested thing stayed the same.

State-based testing is often the valid option for testing Queries.

Interaction-based Testing

Interaction-based testing is often necessary, but it introduces tight coupling between the test and internal implementation of the tested thing. Tests become fragile, and often need to be changed whenever the implementation internals change.

Interaction-based tests use mocks as spies and stubs, when often we could just be using fakes. It might save us from the need to update tests when SUT changes.

Often, tests that could use state-based approach are written in interaction-based style, making the code over-tested.

Testing internal code

In some of our assemblies we might have internal classes. It is totally alright, since we might want just the interface of that class to be public, while the implementation should be hidden.

By default, other assemblies (including unit tests projects) will not have access to internal classes. We still should test them though. In order to do that, we can opt-in to make internal classes visible to selected projects.

The following ItemGroup should be written into the .csproj file that represents the project with internal classes:

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>.My.Tests.Project</_Parameter1>
</AssemblyAttribute>
<!-- We can add more projects -->
<!-- In this case, we can give access to mocking frameworks, like Moq, or NSubstitute -->
<!-- Internally, these frameworks make use of DynamicProxyGenAssembly2 generated by CastleProxy -->
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>DynamicProxyGenAssembly2</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

References

Refactoring tests from interaction-based to State-based (Mark Seemann)

←  Enumerables
Generic Host  →
© 2023 Marcin Jahn | Dev Notebook | All Rights Reserved. | Built with Astro.