Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FileLoadException when running tests on v2.0.0 #3

Open
davidkeaveny opened this issue Jun 14, 2019 · 3 comments
Open

FileLoadException when running tests on v2.0.0 #3

davidkeaveny opened this issue Jun 14, 2019 · 3 comments

Comments

@davidkeaveny
Copy link

Hi, I have recently stumbled across this project, and was very keen to try applying FluentAssertions to NSubstitute verifications, but I am getting the following error when I try to run my tests:

System.IO.FileLoadException : Could not load file or assembly 'NSubstitute, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. 
The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)

I have tried adding assembly binding redirects for NSubstitute as follows:

<dependentAssembly>
  <assemblyIdentity name="NSubstitute" publicKeyToken="92dd2e9066daa5ca" culture="neutral" />
  <bindingRedirect oldVersion="0.0.0.0-4.2.0.0" newVersion="4.2.0.0" />
</dependentAssembly>

I am using NSubstitute 4.2.0 and FluentAssertions 5.7.0 with NUnit 3.12.0, and my application and tests are all based on .NET Framework 4.7.2. All my tests that don't use the Verify.That code pass, and if I rewrite my failing tests to not use Verify.That either then they pass.

Is there anything obvious that I am missing?

@davidkeaveny
Copy link
Author

I think that the main issue is that in NSubstitute 4.x, the ArgumentSpecificationQueue has disappeared; if you remove it, and replace the That method body with the following:

        public static T That<T>(Action<T> action)
        {
            SubstitutionContext.Current.ThreadContext.EnqueueArgumentSpecification(new ArgumentSpecification(typeof(T), new AssertionMatcher<T>(action)));
            return default(T);
        }

then it appears to work again

@davidkeaveny
Copy link
Author

davidkeaveny commented Nov 10, 2020

The correct implementation for NSubstitute as of 4.2.0 is:

public static class Verify
{
    private readonly static ArgumentFormatter DefaultArgumentFormatter = new ArgumentFormatter();

    /// <summary>
    /// Verify the NSubstitute call using FluentAssertions.
    /// </summary>
    /// <example>
    /// <code>
    /// var sub = Substitute.For&lt;ISomeInterface&gt;();
    /// sub.InterestingMethod("Hello hello");
    ///
    /// sub.Received().InterestingMethod(Verify.That&lt;string&gt;(s => s.Should().StartWith("hello").And.EndWith("goodbye")));
    /// </code>
    /// Results in the failure message:
    /// <code>
    /// Expected to receive a call matching:
    ///     SomeMethod("
    /// Expected string to start with
    /// "hello", but
    /// "Hello hello" differs near "Hel" (index 0).
    /// Expected string
    /// "Hello hello" to end with
    /// "goodbye".")
    /// Actually received no matching calls.
    /// Received 1 non-matching call(non-matching arguments indicated with '*' characters):
    ///     SomeMethod(*"Hello hello"*)
    /// </code>
    /// </example>
    /// <typeparam name="T">Type of argument to verify.</typeparam>
    /// <param name="action">Action in which to perform FluentAssertions verifications.</param>
    /// <returns></returns>
    public static T That<T>(Action<T> action)
        => ArgumentMatcher.Enqueue<T>(new AssertionMatcher<T>(action));

    private class AssertionMatcher<T> : IArgumentMatcher<T>, IDescribeNonMatches
    {
        private readonly Action<T> assertion;
        private string allFailures = "";

        public AssertionMatcher(Action<T> assertion)
            => this.assertion = assertion;

        public bool IsSatisfiedBy(T argument)
        {
            using (var scope = new AssertionScope())
            {
                try
                {
                    assertion(argument);
                }
                catch (Exception exception)
                {
                    var f = scope.Discard();
                    allFailures = f.Any() ? AggregateFailures(f) : exception.Message;
                    return false;
                }

                var failures = scope.Discard().ToList();

                if (failures.Count == 0) return true;

                allFailures = AggregateFailures(failures);

                return false;
            }
        }

        public string DescribeFor(object argument) 
            => DefaultArgumentFormatter.Format(allFailures, false);

        private string AggregateFailures(IEnumerable<string> discard)
            => discard.Aggregate(allFailures, (a, b) => a + "\n" + b);
    }
}

see nsubstitute/NSubstitute#438

The only issue I have now is that the AssertionMatcher is being called three times during the test run, so a single assertion failure results in three identical error messages - not a show-stopper, but an annoyance nevertheless.

@rbeurskens
Copy link

Minor detail:

private string AggregateFailures(IEnumerable<string> discard)
    => string.IsNullOrEmpty(allFailures) ? string.Join("\n",discard) : discard.Aggregate(allFailures, (a, b) => a + "\n" + b);`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants