1

I can't figure out how to get some configuration I'm using by following the IOptions pattern.

The service I want to test is the next one:

        private readonly Dictionary<ErrorType, ErrorObject> _errors;

        public UserService(
            ...(Many services)...
            IOptions<Dictionary<ErrorType, ErrorObject>> errors)
        {
            ...
            _errors = errors.Value;
        }

Where ErrorType is an Enum and ErrorObject, an object with the format of the errors I have in my appsettings.json. This is it:

{
  "Errors": {
    "UserNotFound": {
      "ErrorCode": 101,
      "Message": "User not found"
    },
    "WrongPassword": {
      "ErrorCode": 102,
      "Message": "Wrong password"
    }
  }
}

So this works perfectly fine, but my problem now is I don't know how to instantiate it, mock it, or inject it in my test class, since even I'm injecting it I can't resolve it in such class and I always get a null. I injected it like this in the test fixture (same as in my real injection module):

serviceCollection.Configure<Dictionary<BusinessErrorType, BusinessErrorObject>>(x => Configuration.GetSection("Errors").Bind(x));

I got to properly inject the IConfiguration with all the errors, I just can't manage to create the required Dictionary for the constructor.

Here I get the errors without any problem:

IConfiguration Configuration = ioCModule.ServiceProvider.GetRequiredService<IConfiguration>();
            var errorsConfig = Configuration.GetSection("Errors");

And now I gotta figure out what to put in this declaration:

var _options = Options.Create<Dictionary<BusinessErrorType, BusinessErrorObject>>();
Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
Ferran R.
  • 174
  • 1
  • 12

1 Answers1

4

I injected it like this in the test fixture (same as in my real injection module):

You've tried injecting a Dictionary, whereas your class expects getting Options. Try registering and injecting Options and most probably it will succeed.

And now I gotta figure out what to put in this declaration:

Options object is just a holder for some ready to use value, or nothing. You have to construct the Options object with either giving it the value, or giving it nothing (and ending up with empty options object). If you use Options.Create<Dict>() without any parameters, you create an empty object, and that's it. Just create the dictionary before that and pass it to Options facotry.

var dict = new Dictionary<BusinessErrorType, BusinessErrorObject>();
dict.Add("UserNotFound", new BusinessErrorObject(....));
dict.Add("WrongPassword", new BusinessErrorObject(....));

var _options = Options.Create<Dictionary<BusinessErrorType, BusinessErrorObject>>(dict);
// now you have it: _options with some data inside

If you're counting on using Bind() with Options - I doubt it would work just like that, since Bind is used to fill in data structures with configuration read from settings file, and Options is just a shell, something like "Nullable<>", it exists just to pass things around... However I suppose you could first create a dictionary, then fill it with configuration (i.e. Bind) then wrap the just-filled-in dictionary with Options<>, and then register that Options object in the IoC, so your UserService can get its config.. However, I'm not 100% sure if Bind is smart enough to fill generic Dictionaries. You'd need to test it yourself, unfortunatelly I don't have any aspnetcore project at hand to play with

EDIT/Sidenote:

I'm a bit surprised you're building/setting up whole IoC in a UNIT TEST. When creating a Unit Test, you want cut/mock the dependencies as much as possible. That means, no IoC, which can introduce another set of problems/failures/etc to the test.

What I mean is, I would expect your Unit Test to look like:

[Test]
public void FoobarizingTheBaz()
{
    var mock1 = ....;
    var mock2 = ....;
    var mock3 = ....;
    var ... = ....;
    var mockN = ....;

    var dict = new Dictionary<....>();
    dict.Add(....);
    dict.Add(....);
    var mockOptions = Options.Create<...>(dict);

    var tested = new UserService(
        mock1, mock2, mock3, ... mockN, mockOptions
    );

    var result = tested.FoobarizeTheBaz();

    // assert...
}

That is a Unit Test. Only tested object is instantiated, rest is mocked, and you provide only what's absolutely necessary. Sure, I had to write a hell lot of setup/mocks/etc instead of relying on the IoC to automagically materialize them, but once the mocks are written, you can take them out to some common code, test class init, fixture, etc and reuse them.

I mean, no appsettings.json. No IConfiguration. No IoC, etc.

If you instead want to test how many components interact with each other, then it's more like an integration test. In that case, you might want to see this article which touches using custom appsettings.json and IOptions in an integration-test setup. However, mind that "integration tests" in aspnetcore usually means testing how your service responds to requests and checking responses, so you'll see that in this article. Nevertheless, part about IOptions should be helpful.

quetzalcoatl
  • 32,194
  • 8
  • 68
  • 107
  • You made it so simple that you left me feeling I didn't approach my problem the right way. Yes, I actually had mocked all the other dependencies of the service, but since I didn't think I could mock this one I thought the best option was to use the IoC. I actually injected the dictionary this way in my normal IoC module and it worked, so that's why I was trying it to make it work this way as well. The tests already work again, but I'll have a look at the article you sent me anyway, it may be helpful. Thanks a lot for your time and for such a detailed answer! – Ferran R. May 26 '20 at 18:50