SiberianGuy
SiberianGuy

Reputation: 25282

ASP.NET infrastructure in MediatR handlers

I prefer to keep my handlers free from ASP.NET infrastructure that is very hard to test (yes, even in ASP.NET Core). But sometimes it happens and you have a dependency like UserManager (I'd like to know one day why it's not an interface), HttpContext, etc. and unit-tests are turned into a mocking-hell.

I tried using integration testing to deal with it by creating a TestServer and having all the ASP.NET infrastructure initialized for every api call. It works quite well but sometimes seems like an overkill if I want to test simple logic of my handler. And while it solves technical problem of mocking ASP.NET infrastructure, it keeps architectural problem (if you consider it so) of having ASP.NET infrastructure into your handlers.

I'd like to know what are the recommended approaches to deal with it?

Upvotes: 11

Views: 2071

Answers (3)

trevorc
trevorc

Reputation: 3031

I feel your pain. I stumbled across a fantastic blog post from Jimmy Bogard that handles this problem by using what Martin Fowler calls Subcutaneous Tests. I will leave the deep explanation to those experts but in a nutshell subcutaneous tests simply avoid all the difficult to test aspects of the UI.

Shameless plug: I am currently in the process of writing up a wiki that demonstrates these patterns in a sample end-to-end project on github. It's not difficult to follow but is probably too much code to post for a SO answer.

To Summarize:

  • If you are using MediatR correctly your controllers should be very thin which makes testing them pointless.
  • What you want to test are your handlers.
  • However, you want to test your handlers as part of your real world pipeline.

To Solve:

  1. Wrap the http request in a transaction.
  2. Build a test fixture that mimics the applications Startup.cs
  3. Setup a test db server to execute queries and commands against but also is reset after each test.

That's basically it. Everytime you run an integration test against one of your handlers:

  • The hosting environment is mocked but your application is started up in a real world test.
  • Your query or command is wrapped in a transaction mimicking your DbContext.
  • The handler is executed against a real database and then reset.

I would add more code examples to my answer but between the blog post and the wiki I provided, it is much easier to follow the code examples there.

Edit 8/2021:

Stick with the source. Jimmy Bogard keeps the contoso university project current on his github page. Another great and a little more advanced example is the modular monolith project by Kamil Grzybek. That also is updated regularly on his github page.

Upvotes: 7

Arwin
Arwin

Reputation: 1013

Mediatr or no, you should always try to have only very basic pass this along logic in your controllers and call injected business logic classes from there to do the actual work. As you inject them with interfaces to this business logic, your controllers' dependencies are easily mocked in your unit tests, and your tests can focus on if they implement those interfaces properly and do only the basic work of routing input/output. And your actual business logic can be tested even easier.

For those classes that are static, for instance for reading the web.config settings, one strategy that I like a lot is make an interfaced wrapper class around them. While ConfigurationManager is static, I can still just write a regular class with an interface that I put methods or properties on to read a specific setting (preferably semantically named) from the Configuration Manager. Now I can easily mock any configured setting (or absence of it) in my test by just mocking the interface and setting up different return values.

Upvotes: 2

Mickaël Derriey
Mickaël Derriey

Reputation: 13704

I'd say it depends on the level of confidence you want to get in the end. If you want to make sure the whole system works as expected, then integration tests using a TestServer are probably the way to go.

One advantage of MediatR, though, is it allows you to decouple your business logic from the application using it, which is why at the very top level, let's say in controllers, there's no logic but just a delegation to the mediator.

That being said, you're right that sometimes your logic needs information from the hosting application. An example would be the user making the request, which is accessible in the HTTP context.

In that case, if you want to avoid having to set up a test HTTP server to test your logic works, you could represent that information in an abstraction and your handler would then take a dependency on that abstraction. Your tests could then mock that dependency while using the real system for everything else.

Does that make sense?

Upvotes: 1

Related Questions