4

I have some problems testing a singleton. When I run this code, I get an error in TestGetLogicalDevices(). CallTo() failed because service is no fake object. When I try to create a fake object (commented code), it gives an error because RestService is a singleton with private constructor. How can I create a fake object of this singleton?

    private RestService service;

    [TestInitialize]
    public void Init()
    {
        //service = A.Fake<RestService>();
        service = RestService.Instance;
        service.CreateClient("test", "test");
    }

    [TestMethod]
    public async Task TestGetLogicalDevices()
    {
        var logicalDevices = (List<LogicalDevice>)A.CollectionOfFake<LogicalDevice>(10);
        A.CallTo(() => service.GetLogicalDevices()).Returns(Task.FromResult(logicalDevices));
        List<LogicalDevice> collectedData = await service.GetLogicalDevices();
        Assert.AreEqual(2, collectedData.Count);
    }

    public async Task<List<LogicalDevice>> GetLogicalDevices()
    {
        var response = await client.GetAsync(apiBaseUrl + "/logical-devices");
        if (response.IsSuccessStatusCode)
        {
            var json = await response.Content.ReadAsStringAsync();
            var logicalDevices = JsonConvert.DeserializeObject<List<LogicalDevice>>(json);
            var sortedList = logicalDevices.OrderBy(logicalDevice => logicalDevice.Name).ToList();
            return sortedList;
        }
        else
        {
            return null;
        }
    } 

Update I added the code of my method I want to test. Maybe someone has suggestions for better tests?

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • Please post the code for RestService, at least the relevant bits. – tom redfern Mar 12 '17 at 14:15
  • You cannot prime a concrete class to behave how you want it to using a mocking framework. This is what fakes are for. They are fake versions of dependencies which can be used to test classes which depend on them. It's not clear what you are trying to assert with this test. – tom redfern Mar 12 '17 at 14:17
  • 1
    @tomredfern I added the code of the method I want to test. Maybe you got a better solution for testing this? – Joris Meylaers Mar 12 '17 at 15:03
  • What is the type of the client in *client.GetAsync(...)* ? – tom redfern Mar 12 '17 at 16:00
  • 1
    @tomredfern It's an HttpClient with authentication. Here is the code: `byte[] authBytes = Encoding.UTF8.GetBytes(Username + ":" + Password); client = new HttpClient(); var authHeaderValue = Convert.ToBase64String(authBytes); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authHeaderValue);` – Joris Meylaers Mar 12 '17 at 16:08

1 Answers1

4

Note: I'm not sure I understand what you're trying to do. What are you trying to test exactly? In your test, you configure service.GetLogicalDevices() to return something, then you call service.GetLogicalDevices() and assert what it returns (which, unless FakeItEasy is broken, should be what you configured it to return). So, you're not actually testing the service... you're testing the mocking framework! Mocking frameworks like FakeItEasy are useful to mock the dependencies of the system under test (SUT), not the SUT itself. In your case, if the SUT is RestService, you need to mock the dependencies of RestService, not RestService itself. For instance, you could inject an HttpClient with a HttpMessageHandler that you control (see here for more details).


Now, to answer your actual question (assuming it's really RestService that you want to fake):

When I run this code, I get an error in TestGetLogicalDevices(). CallTo() failed because service is no fake object.

A.CallTo only works on fakes; FakeItEasy can't control the behavior of objects it didn't create.

When I try to create a fake object (commented code), it gives an error because RestService is a singleton with private constructor

RestService is a class, and FakeItEasy can create a fake for a class, but it does it by inheriting the class, so it needs an accessible constructor. Also, keep in mind that only virtual methods can be configured. GetLogicalDevices is not virtual, so the fake can't override its behavior.

You have two main options for faking RestService:

  • make the constructor protected rather than private, and make the methods virtual so that they can be overriden
  • create an IRestService interface that represents the "public contract" of the RestService class, and fake that interface instead of the class.
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • 1
    Actually I was trying to test the GetLogicalDevices() method but I don't know where to start. I thought the fakes would give me the possibility to get response data without doing the api call. Maybe you can give me an example? @ThomasLevesque – Joris Meylaers Mar 12 '17 at 16:27
  • 2
    @JorisMeylaers if you're testing `GetLogicalDevices()`, you mustn't mock it, it has to be the real one. What you need to mock is the dependencies. In this case, it's the `HttpClient`, which can't be mocked directly, but you can mock the `HttpMessageHandler` that is used by the `HttpClient`. The article I pointed to shows an example of that. I'll try to post a more specific example. – Thomas Levesque Mar 12 '17 at 16:53
  • 2
    @JorisMeylaers, here's a full example for your case: https://gist.github.com/thomaslevesque/622f8a0118ee1f2d46d723db2a976985. Note that you can't keep RestService as a real singleton, since the constructor needs to be public, but anyway you probably shouldn't; singleton is a terrible pattern when it comes to unit testing. You should use dependency injection instead. – Thomas Levesque Mar 12 '17 at 17:05