Correctly asserting validation throws a validation exception

181 Views Asked by At

I have this code "dummied" down to it most basic parts but I'm not sure how to resolve the the unit test fails. Mostly because the error message suggestion doesn't match what I'm actually doing.

DotNetFiddle Example

using System;
using System.Threading.Tasks;
using FluentValidation;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using FluentAssertions;
                    
public class Program
{
    private IValidator<Entity> _validator;
    
    public static async Task Main()
    {
        var program = new Program();
        await program.Test();
    }
    
    public async Task Test()
    {
        var entity = new Entity();
        _validator = Substitute.For<IValidator<Entity>>();
        _validator
            .ValidateAndThrowAsync(Arg.Any<Entity>())
            .ThrowsAsyncForAnyArgs(new ValidationException(string.Empty));
        
        var testMethod = async () => await CreateAsync(entity);
        
        await testMethod.Should().ThrowAsync<ValidationException>();
    }
    
    public async Task CreateAsync(Entity entity)
    {
        await _validator.ValidateAndThrowAsync(entity);
    }
    
    public class Entity {}
}

Returns an exception/stack trace:

Unhandled exception. NSubstitute.Exceptions.CouldNotSetReturnDueToMissingInfoAboutLastCallException: Could not find information about the last call to return from.

Make sure you called Returns() after calling your substitute (for example: mySub.SomeMethod().Returns(value)), and that you are not configuring other substitutes within Returns() (for example, avoid this: mySub.SomeMethod().Returns(ConfigOtherSub())).

If you substituted for a class rather than an interface, check that the call to your substitute was on a virtual/abstract member. Return values cannot be configured for non-virtual/non-abstract members.

Correct use: mySub.SomeMethod().Returns(returnValue);

Potentially problematic use: mySub.SomeMethod().Returns(ConfigOtherSub()); Instead try: var returnValue = ConfigOtherSub(); mySub.SomeMethod().Returns(returnValue);

at NSubstitute.Core.ThreadLocalContext.LastCallShouldReturn(IReturn value, MatchArgs matchArgs) at NSubstitute.SubstituteExtensions.ConfigureReturn[T](MatchArgs matchArgs, Func'2 returnThis, Func'2[] returnThese) at NSubstitute.SubstituteExtensions.ReturnsForAnyArgs[T](T value, Func'2 returnThis, Func'2[] returnThese) at NSubstitute.ExceptionExtensions.ExceptionExtensions.ThrowsAsyncForAnyArgs(Task value, Exception ex) at Program.Test() at Program.Main() at Program.()

1

There are 1 best solutions below

0
Erik Philips On

Took me a darn while to figure this out but I looked at the signature for ValidateAndThrowAsync():

public static async Task ValidateAndThrowAsync<T>(this IValidator<T> validator, T instance, CancellationToken cancellationToken = default) {
        await validator.ValidateAsync(instance, options => {
            options.ThrowOnFailures();
        }, cancellationToken);
    }

There is no return value. To configure a void (Task<void>) returning (async) method, I have to use the When/Do:

        _validator.WhenForAnyArgs(x => x.ValidateAndThrowAsync(entity))
            .Throw(new ValidationException(string.Empty));

DotNetFiddle Fixed