4

The setup is as follows:

  • a .NET Standard 2.0 library
  • a NUnit test project for it, targeting .NETFramework 4.6.1

Latest VS 2017, latest NUnit.

I've been working on the project on weekend from home, uploaded my work to git and today started working from work (I've already worked from both places before). Only now I found that something was wrong with the project (I don't remember very well what was wrong at the start, but it seems the problem was the same as I have now, described later).

After fiddling around to unrepairable state with it, I wholly deleted it and cloned the git repo anew.

The project compiles fine, but at runtime tests throw "Method not found" exception. A bit of poking around showed that the problem only manifests on one overload of the following method:

    public static YNABClient GetInstance(HttpMessageHandler _handler)
    {
        if (instance is null)
        {
            instance = new YNABClient(_handler);
        }

        return instance;
    }

    public static YNABClient GetInstance() => GetInstance(new HttpClientHandler());

The one without parameters is fine, the one with is not. Deleting and adding library as a reference to tests, deleting and adding both test and library project. Other solutions for similar situations I found on the internet all pertain to ASP.NET MVC, which is not my case, though this question did lead me to checking overloads and finding that one of them actually works.

At home everything still works fine, though I have yet to try to delete and reinstall the project as I did at work. This leads to 2 possible sources for problems: environment, though I haven't managed to find a meaningful difference, or git, though I use a "stock" git ignore for VS (this one), so there shouldn't be problems there. The basic setup for my projects didn't change during weekend and worked before, so something broke from recent fiddling.

Also, if I add a console application(.Net Framework 4.6.1) to solution and try calling the problematic method from it, it actually works fine.

If that would help, my github for the project is here

I've been asked for calling examples in the comments. Basically, I have 2 Test Fixture classes with different setups - one for real API calling for ease of debugging actual use, and one with faking it, as per good test practices. Works:

    [OneTimeSetUp]
    public void Setup()
    {
        ynabClient = YNABClient.GetInstance();
        ynabClient.RefreshAccessToken(ApiKeys.AccessToken);
    }

Throws exception:

    [OneTimeSetUp]
    public void Setup()
    {
        handler = new StubHandler();
        ynabClient = YNABClient.GetInstance(handler);
    }

Some poking around shows that my problem is quite likely related to System.Net.Http versioning discrepancy with .NET Framework and .NET Standard, that is quite a widespread problem if you google it. However none of the examples I dug up exhibit my particular symptoms, and I'm not yet sure what to do. And why everything works fine on my home PC.

Misamoto
  • 572
  • 6
  • 16
  • Since the problem only occurs at runtime, you need to post where you are calling the `GetInstance` method using reflection (or using `dynamic`). For some reason, the runtime binder is not able to match the parameters up to the static method. – D Stanley Jul 23 '18 at 15:32
  • As I've said, I'm calling it from NUnit test project. It's public, no need for reflection. While I don't know how NUnit is working internally, everything is pretty straightforward there. I'll add examples – Misamoto Jul 23 '18 at 15:36
  • When I run your tests, the mock tests all pass, but the real ones all fail: "YNAB didn't authorize us. Is access token fine?" It sounds like the mock tests are failing for you, which I can't reproduce with your code. Please try to reduce this to a [mcve]. – Jon Skeet Jul 23 '18 at 16:11
  • Your attempt actually does verify that the problem is with environment. All test act as they should for you - mocks pass since they don't have external dependencies, and real ones return an unauthorized exception since you didn't provide an access key. – Misamoto Jul 23 '18 at 16:26
  • is it because of your framework? your YNABConnector is .NET Standard 2.0, but your UnitTest is 4.6.1. – Dongdong Jul 23 '18 at 17:17
  • @Dongdong: That's fine - .NET 4.6.1 supports .NET Standard 2.0. See https://learn.microsoft.com/en-us/dotnet/standard/net-standard – Jon Skeet Jul 23 '18 at 17:36
  • @DaisyShipton yes, but it used System.Net.Http, which does not match between test and source libraries. but it works if I update test library to .NET core. – Dongdong Jul 23 '18 at 18:15
  • @Dongdong: Yes, there are oddities with System.Net.Http - but your comment made it sound like you wouldn't expect .NET 4.6.1 to support .NET Standard 2.0. I suspect that modifying the System.Net.Http dependency would work as an alternative. – Jon Skeet Jul 24 '18 at 05:29
  • No, I don't have any problems with versions. Version ambiguity is a compile-time error. I can "manually" cause it by installing a different version of the library from Nuget (instead of using the one in the framework), and then the solution doesn't compile at all. It does nothing for overloaded method though. – Misamoto Jul 24 '18 at 07:10
  • Some more poking around shows that indeed, the problem IS likely with versions of the library, since changing the type of the parameter in overload to dynamic lets the overload get called (it's not a solution, just an experiment) – Misamoto Jul 24 '18 at 10:03

3 Answers3

0

The issue that you have is that your GetInstance method accepts HttpMessageHandler as parameter, and in your test you are passing HttpClientHandler object. So, you have declared one parameter, but provide different object when you call the method.

Caldazar
  • 2,801
  • 13
  • 21
0

I got this error in my environment:

Severity Code Description Project File Line Suppression State Error CS0433 The type 'HttpMessageHandler' exists in both 'System.Net.Http, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' and 'System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'

Your YNABConnector is .NET Standard 2 but Unit test is 4.6.1, they are using different signature for same assembly.

Here is official document to test: https://github.com/nunit/docs/wiki/.NET-Core-and-.NET-Standard

  1. created a .NET Core Library(be aware that: cannot be .NET Standard library by above link),
  2. copy your test code there,
  3. follow the official document to add references,

your code works then, no errors.

But in your base code, I still prefer this code:

public class YNABClient
{
    private static YNABClient instance;

    private HttpClient client;

    private HttpMessageHandler handler;

    private YNABClient(HttpMessageHandler _handler = null)
    {
        handler = _handler ?? new HttpClientHandler();
        client = new HttpClient(_handler);
    }

    public static YNABClient GetInstance(HttpMessageHandler _handler = null)
    {
        return instance ?? (instance = new YNABClient(_handler));
    }

    ......
}
Dongdong
  • 2,208
  • 19
  • 28
  • HttpMessageHandler is an abstract class. HttpClientHandler is the default value for it. Instanciating client with another handler is required for swapping dependency during testing - we don't want to actually post anything to a real server while testing our parser. While that's suboptimal realization which I might want to do something with in the future, right now that's as intended. Read the question again - the NO parameters version of method actually WORKS. – Misamoto Jul 23 '18 at 15:27
  • Well, that's really off-topic now, but take a look at StubHandler and it's use in tests library to understand why the parameter is declared as it is. Getting rid of overloads is too bruteforce for my taste. There's no reason for them to not work, you know, it's just a bug to be squashed. Which is proven by Daisy Shipton in the comments to the question, since everything works for him – Misamoto Jul 23 '18 at 16:36
  • is it because of your framework? your YNABConnector is .NET Standard 2.0, but your UnitTest is 4.6.1? – Dongdong Jul 23 '18 at 17:15
0

So, it turns out, as DongDong suggested, the problem is indeed with interfacing between .NET Framework and .NET Standard. The problem is not really that they're incompatible, or that Test project needed some additional dependencies, but that they're shipped with different versions of System.Net.Http.

The diagnostics were hindered by showing no visible errors. However, changing parameter types showed that indeed, the problem is only with classes from that namespace.

Another problem with diagnosing the issue was that the project works fine on some machines (my home PC and Daisy Shipton's from comments to the question).

However after determining the source of the problem I was able to google what problems exist with the library in the first place, and eventually found a trove of uncompatibility issues on .NET github.

I tried the solution used in those cases and added a "binding redirect" to a concrete version of the library, and after that it works fine. I added the redirect to app.config of my Tests project. For reference it looks like this:

<dependentAssembly>
    <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-4.2.0.0" newVersion="4.2.0.0" />
</dependentAssembly>

I still have no understanding of why the project worked fine on some machines.

Misamoto
  • 572
  • 6
  • 16
  • .NET Standard 2 is newer than 4.6.1, it should have new features that could not work in older version. but I should'v got your upvote! – Dongdong Jul 24 '18 at 14:35
  • You did :) You also have a downvote from someone, so that's 0. The other answer is more deserving of it, but well... Also, . Net Standard should be fully compatible with 4.6.1 per documentation https://learn.microsoft.com/en-us/dotnet/standard/net-standard , so newer-older has nothing to do with it :) – Misamoto Jul 24 '18 at 15:08