Using FakeItEasy, is it possible to create a dummy

2019-07-04 05:38发布

I have the following test:

[Fact]
public void StartProgram_CallsZoneProgramStart()
{
    var zone = A.Fake<Zone>();
    zone.StartProgram();
    A.CallTo(() => zone.ZoneProgram.Start(null, A.Dummy<ActionBlock<InterruptInfo>>())).MustHaveHappened(Repeated.Exactly.Once);
}

It's creating a dummy of type ActionBlock<InterruptInfo> which is being passed into the MustHaveHappened call. zone.StartProgram definitely calles the zone.ZoneProgram.Start method, but this call is not seen by FakeItEasy. It returns the following error message:

Assertion failed for the following call:
  ZoneLighting.ZoneProgramNS.ZoneProgram.Start(<NULL>, ActionBlock\`1 Id=1)
Expected to find it exactly once but found it #0 times among the calls:
  1: ZoneLighting.ZoneProgramNS.ZoneProgram.Start(inputStartingValues: Faked ZoneLighting.ZoneProgramNS.InputStartingValues, interruptQueue: ActionBlock`1 Id=2)
  2: ZoneLighting.ZoneProgramNS.ZoneProgram.Start(inputStartingValues: <NULL>, interruptQueue: ActionBlock`1 Id=2)

As can be seen from the error message, the ID on the ActionBlocks being compared are different (1 and 2), which is why it's not able to see that the call is made. My question is, why is the ID of the dummied ActionBlock = 1? I thought being a dummy object, it shouldn't have any concrete details in it like ID etc. Is this because generic types cannot be dummied?

I saw something similar here: https://github.com/FakeItEasy/FakeItEasy/issues/402

But I wasn't able to figure out if that's talking about the same thing or not. Any help would be greatly appreciated.

1条回答
叛逆
2楼-- · 2019-07-04 06:08

I am not familiar with ActionBlocks, so am not sure where they get their Id values from, but I think I can shed some light on what's going on in your test.

First, I think you're confused about what a Dummy is. If so, don't feel bad. They can be a little confusing. From the Dummy documentation, a Dummy is

Dummy is an object that FakeItEasy can provide when an object of a certain type is required, but the actual behavior of the object is not important.

They are mostly used by FakeItEasy itself when it needs to create an object to feed to class constructors (we'll see more about that later), or when it needs to return a non-fakeable object from a method or property. It's rare for end users to have to create them.

A Dummy is an actual object (it has to be—otherwise, how could we do anything with it?). It has to have whatever concrete details in it that its type has (in this case, ActionBlock<InterruptInfo>). There are no restrictions against Dummying generic types.

Looking at the docs for how a Dummy is made, we can see that since ActionBlock<InterruptInfo> probably doesn't have a custom IDummyDefinition available (do you have one?), and it's not a Task, and it's not fakeable (because the class is sealed), then the Dummy is made by invoking one of the ActionBlock constructors, with Dummies being made to satifsy each of the arguments.

I guess that ActionBlocks have IDs. How they're assigned, I have no idea, but if they're a good ID, then it looks like we have two different ActionBlock<InterruptInfo>s: one provided in zone.StartProgram, and the Dummy made in the test.

The ActionBlocks documentation suggests that it doesn't override Equals, so a reference comparison will be performed, and the two ActionBlocks (the Dummy and the one used in the production code) don't match. This is why FakeItEasy doesn't recognize the call.

If you were just trying to see if any call to zone.ZoneProgram.Start was made with the first argument null and the second argument some ActionBlock, I think you might've meant to use:

A.CallTo(() => zone.ZoneProgram.Start(null, A<ActionBlock<InterruptInfo>>.Ignored))
            .MustHaveHappened(Repeated.Exactly.Once);

(Ignored can also be shortened to _. Read more about ignoring argument values if you're so inclined.)

That may get you past your immediate problem, although I have concerns about two things:

  1. It looks like zone.ZoneProgram.Start is being called twice, not "Exactly.Once", but I'm sure you'll be able to deal with this, and
  2. In general, faking the object under test is considered to be an anti-pattern. Typically one supplies fake dependencies to the production code under test. I'm not saying it won't work, but sometimes it can lead to confusion. Still, it may be a problem for another day, after the current question has been resolved.

I hope that helps a little.

Oh, and you asked about Issue 402. That issue is about giving users more power when defining customization classes that will control how dummies are created. Unless you've made a class extending IDummyDefinition or DummyDefinition, it's probably not relevant at this point.

查看更多
登录 后发表回答