I have a simple repository InvoiceRepository
which is my SUT (software under test), its using Dapper extension methods and I want to unit test the repository methods and mock out the database calls. As Im not using Entity Frameworkβs UseInMemoryDatabase so I need to mock the connection which dapper extends. Additionally I have a factory that returns the connection because thats how the cool kids do it.
See Minimal API example for a functional example of UseInMemoryDatabase.
So the code looks like this, ooh fancy its got a primary contructor, must be nice to work on modern code π€
1 | public class InvoiceRepository(ISqlConnectionFactory factory) : IInvoiceRepository |
Note that CreateConnection
returns IDbConnection and QueryFirstAsync is the dapper extension method.
Being OG noob you might be tempted to try mock the dapper method QueryFirstAsync
. The code below will compile but at test runtime Moq will protest as its Guard.IsOverridable -> Guard.cs will throw with Extension methods (here: SqlMapper.QueryFirstAsync) may not be used in setup / verification expressions.
1 | using Dapper; |
So we need a work around.
CAVEAT: You could argue that unit tests in the repository are low value and this is more of an integration test concearn, WebApplicationFactory and Test Containers is your friend if you go down this route, if you are a component tests kind of nerd then Component Tests With Collection & Class Fixtures is probably your jam!
On the other hand it would be nice to know that your software behaves as expected at a unit test level, things like the command text and parameters passed have not changed. The key is to be pragmatic and weigh up the options with your team and companys suggested way of working.
Iβll roll with some work-arounds, else whats the point of this blog post besides to brag about my cool primary contructor!
Workaround 1 : Wrapper
An acceptable approach is to add another layer of abstraction, you could name it based on what it does so DapperWrapper
fits this description, Im childish so had a good LOL at this name ππ€£. This abstraction would then have an interface that we can mock.
- Define the wrapper, here I used the same name,
QueryFirstAsync
for my function but you could call it something likeQueryFirstWrapperAsync
to mitigate confusion, or I dont know - you could read the class name.
1 | using Dapper; |
- Then inject and use the wrapper in the repository, this functions the same as before but now has the additional layer of abstraction.
1 | public class InvoiceRepository(ISqlConnectionFactory factory, IDapperWrapper wrapper) : IInvoiceRepository |
- Update the test to now mock the wrapper, this will now return a new instance of
MyInvoice
. The tests as they stand could now just assert the response and would be adding a guard for the sql command text, honestly the integration test in the caveat above could have done that ΚΚΜ ΝΚΜ Κ
1 | [ ] |
- I would go a step futher and now also check the
param
argument, as my mock setup I usedIt.IsAny<object>()
because in the implentation the argument is newβd up, so the memory address is not the same. Remember kids, Steve Smith says new is glue.
You can use Moq.Langauge Callback Function to capture these and assert on them by value, its like magic.
Workaround 2 : delegate
Spoiler: This is pretty much the same as the wrapper but with some additional complications.
I would generally steer clear of delegates, they harder to understand and I favour code that is easy to read for my old-dad eyes, however for completness I did some delegate magic.
Potentially this code could pass the delegate as Func<T, TResult>
which just defines a function that takes a parameter T
and returns TResult
but I thought this was was more understandable to imperative programmers like me. Also, I do what I want. I am Batman π¦
- Create the dapper delegate implementation and base class.
QueryFirstAsyncDeligate
could be an interface but I just popped it in the base class, I dont normally code with delegates so I made π© up as I went along from here.
I just called my class file DapperDeligate.cs
and popped it in the root of my Infrastructure
project.
1 | using Dapper; |
- In the repository, inject and use the delegate
1 | public class InvoiceRepository(ISqlConnectionFactory factory, DapperDeligateBase dapperDeligate) : IInvoiceRepository |
- Wire up the services registration, this just means when you ask for
DapperDeligateBase
in your code, you will getDapperDeligate
1 | builder |
- Update the tests, bet you wrote the tests first you TDD champion!
1 | public class InvoiceRepositoryTests |
Then as with the wrapper, I would also use Moq.Langauge Callback Function to capture the IDbConnection
, string
command text and object
parameters being passed to QueryFirstAsyncDeligate
to validate their shape.
After writing this code I feel more like Robin π¦