0

I'm learning how to write unit tests, please don't judge heavily. I have an API method below, written in C# for .NET 8.0, which I want to test. I use XUnit, Entity Framework Core 8, SQL Server 2019. Currently I'm rewriting it to separate the data layer, so I will not have to moq DB context.

[HttpGet("GetABunch")]
public async Task<ActionResult<TodoItem>> GetABunch()
{
    string[] myAnimals = { "dog", "cat" };

    var animals = await _context.TodoItems
        .Where(i => myAnimals.Contains(i.Name))
        .ToListAsync();

    return Ok(animals);
}

My unit test is not working. I'm getting an error:

System.ArgumentException : Can not instantiate proxy of class: TodoApiDb.Models.TodoContext.
Could not find a parameterless constructor. (Parameter 'constructorArguments')

But before fighting this error, and creating context constructor without parameters, I believe my test is conceptually wrong, please help to fix it.

Here's my unit test:

[Fact]
public async Task GetABunch_ReturnsFilteredTodoItems()
{
    // Arrange
    var mockData = new List<TodoItem>
    {
        new TodoItem { Id = 1, Name = "dog" },
        new TodoItem { Id = 2, Name = "cat" },
        new TodoItem { Id = 3, Name = "bird" }
    }.AsQueryable();

    var mockSet = new Mock<DbSet<TodoItem>>();
    mockSet.As<IQueryable<TodoItem>>().Setup(m => m.Provider).Returns(mockData.Provider);
    mockSet.As<IQueryable<TodoItem>>().Setup(m => m.Expression).Returns(mockData.Expression);
    mockSet.As<IQueryable<TodoItem>>().Setup(m => m.ElementType).Returns(mockData.ElementType);
    mockSet.As<IQueryable<TodoItem>>().Setup(m => m.GetEnumerator()).Returns(mockData.GetEnumerator());
    mockSet.As<IAsyncEnumerable<TodoItem>>().Setup(m => m.GetAsyncEnumerator(It.IsAny<CancellationToken>()))
        .Returns(new TestAsyncEnumerator<TodoItem>(mockData.GetEnumerator()));

    var mockContext = new Mock<TodoContext>();
    //mockContext.Setup(c => c.TodoItems).Returns(mockSet.Object);
    mockContext.Setup(c => c.Set<TodoItem>()).Returns(mockSet.Object);

    var controller = new TodoItemsController(mockContext.Object);

    // Act
    var result = await controller.GetABunch();

    // Assert
    var actionResult = Assert.IsType<OkObjectResult>(result.Result);
    var returnedItems = Assert.IsType<List<TodoItem>>(actionResult.Value);
    Assert.Equal(2, returnedItems.Count);
    Assert.Contains(returnedItems, i => i.Name == "dog");
    Assert.Contains(returnedItems, i => i.Name == "cat");
}

Testing app code on GitHub.

1 Answer 1

4

Well, this is opinionated and some people will think I am the devil, because I don't like their shiniest toy...

What you really need to do is to drop Moq.

Look at your code. You wrote two lines of code. And then 5+ lines of Moq code. For no good reason. All your test is testing now is that Moq has no bugs, and the c# return statement works. Because the two lines you wrote and that should be tested, don't even get called any more. And whether they actually work on a real database is anyone's guess, because you did not test it.

What you should test, is whether your code does what you want it to do. I know people have told you that databases are these far away dependencies in far away lands on for away servers. They don't have to be. You can just as easily spin up a SQLite in-memory database, that only exists for the duration of your one test. It probably takes less lines than all your moq-ing and it actually tests the code you wrote on a real database, instead of replacing it with Moq.

Mocking is a really powerful tool and there will be instances where the state you need for your test cannot produced in a "legal" way. Then Moq (or other frameworks) will help. You will know it, when you see it. In all other instances, Moq (or other frameworks) are a crutch holding you back from actually testing your code.

If you ever change your database tables, you have to rework all your unit tests. The point of a test is to still work after you have refactored your code, assuming you have refactored it correctly. This Moq stuff is like glueing Legos together. It will break the instance you change your actual logic. It's the anti-thesis to a good test.

Sorry, I am sure someone else will come by, tell you how to fix this error and properly mock/moq a database connection. Maybe this is what you want. Just remember, it is not your job to test Moq, it is your job to test your code.

How can I create an in memory sqlite database?

DBContext has a constructor that takes an existing connection: https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.dbcontext.-ctor?view=entity-framework-6.2.0

Sign up to request clarification or add additional context in comments.

5 Comments

I agree with it. Like I mentioned it's a conceptual/educational question, now I'm rewriting my app by adding data layer, which I will unit test separately, so I will avoid moqing context.. at least. BTW Microsoft do not recommend using in-memory database, which is strange, as it is nice testing solution.
Microsoft does not recommend using their in-memory database. They don't say you should not use others or don't use on in general. You just shouldn't use theirs.
I guess you could solve your imminent problem by providing a parameterless constructor to your TodoContext. If it's completely mocked, it doesn't need parameters anyway.
it didn't help.. I'm getting other errors after it. I'm however added Data layer with service, which I may test separately. Struggle with .Net Core dependency injection though, to add this service in a scope by AddScope ..
I'm afraid I cannot really help with errors I do not see. Maybe you want to open another question for your current problem?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.