8

When using IDataProtectionProvider in a Web API, the IoC container is configured with AddDataProtection (services.AddDataProtection();) and enables the use of DI to retrieve a IDataProtectionProviderin a service as such:

private readonly IDataProtectionProvider _dataProtectionProvider;

public CipherService(IDataProtectionProvider dataProtectionProvider)
{
    _dataProtectionProvider = dataProtectionProvider;
}

If I would like to test my CipherService (in my case using Xunit), I will not be able to make this work without using DI, so my question is;

Q: How can I use DI or otherwise make IDataProtectionProvider in a test project?

Nkosi
  • 235,767
  • 35
  • 427
  • 472
Marcus
  • 8,230
  • 11
  • 61
  • 88
  • So why not just mock the dependency and inject it into the dependent class? – Nkosi Aug 09 '17 at 13:54
  • Because this is an integration test. – Marcus Aug 09 '17 at 13:56
  • Review the [source code](https://github.com/aspnet/DataProtection/blob/dev/src/Microsoft.AspNetCore.DataProtection/DataProtectionServiceCollectionExtensions.cs) and see which dependencies you need to mock/stub/fake – Nkosi Aug 09 '17 at 13:59

2 Answers2

11

EphemeralDataProtectionProvider can be used in a unit testing scenario as it generates a random secret for each instance.

Example:

var dataProtectionProvider = new EphemeralDataProtectionProvider();

var service = new CipherService(dataProtectionProvider);

// test as usual

This was specifically provided by Microsoft for your exact use-case.

There are scenarios where an application needs a throwaway IDataProtectionProvider. For example, the developer might just be experimenting in a one-off console application, or the application itself is transient (it's scripted or a unit test project). To support these scenarios the Microsoft.AspNetCore.DataProtection package includes a type EphemeralDataProtectionProvider. This type provides a basic implementation of IDataProtectionProvider whose key repository is held solely in-memory and isn't written out to any backing store.

Matthew
  • 24,703
  • 9
  • 76
  • 110
  • 1
    I believe this should be the accepted answer because it gives a way to protect/unprotect your data in the "arrange / prepare" phase of your test, then apply / test the opposite operation in the test itself. Anyway, thanks for your help! – TheMSG Aug 09 '21 at 23:15
10

Here how I did it using Moq framework:

Mock<IDataProtector> mockDataProtector = new Mock<IDataProtector>();
mockDataProtector.Setup(sut => sut.Protect(It.IsAny<byte[]>())).Returns(Encoding.UTF8.GetBytes("protectedText"));
mockDataProtector.Setup(sut => sut.Unprotect(It.IsAny<byte[]>())).Returns(Encoding.UTF8.GetBytes("originalText"));

Mock<IDataProtectionProvider> mockDataProtectionProvider = new Mock<IDataProtectionProvider>();
mockDataProtectionProvider.Setup(s => s.CreateProtector(It.IsAny<string>())).Returns(mockDataProtector.Object);

And where I need to pass in the IDataProtectionProvider, I use:

mockDataProtectionProvider.Object

For an integration test scenario, where you want a real DataProtectionProvider, you can use the following MSDN Documentation article.

Hope this helps.

Artak
  • 2,819
  • 20
  • 31
  • 3
    It does not work if you use extended Protect method that takes a string instead of a byte array, any idea ? – Jonathan Sep 26 '19 at 19:12
  • This does not work for me, because it is a unsported Expression. (here: DataProtectionCommonExtensions.CreateProtector) may not be used in setup / verification expressions. Can you explain how you resolved this? – Crztean Jan 28 '21 at 05:18