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

Property setups are ignored on mocks instantiated using Mock.Of #1066

Closed
stakx opened this issue Oct 6, 2020 · 7 comments · Fixed by #1234
Closed

Property setups are ignored on mocks instantiated using Mock.Of #1066

stakx opened this issue Oct 6, 2020 · 7 comments · Fixed by #1234
Assignees
Labels
Milestone

Comments

@stakx
Copy link
Contributor

stakx commented Oct 6, 2020

This report goes back to a Gitter chat post by @bluechrism on Sep 3 2020:

image

[...] can someone explain this difference between new Mock<T> and using Mock.Of<T> with Mock.Get to set up a property: Again, both the Tests would pass with Mock 4.7.8 but the second now fails with 4.14.5.

Moq 4.12.0 introduced a behavioral change that leads to property setups sometimes being ignored on mocks that were created with Mock.Of<> (or on mocks on whom SetupAllProperties was called):

Repro code:

public interface IX
{
    int Property { get; set; }
}

[Fact]
public void Stubbed_property_set_before_SetupGet()
{
    var mock = Mock.Get(Mock.Of<IX>());
    mock.Object.Value = 4;
    mock.SetupGet(m => m.Property).Returns(3);
    Assert.Equal(3, mock.Object.Value);
}

[Fact]
public void Stubbed_property_set_after_SetupGet()
{
    var mock = Mock.Get(Mock.Of<IX>());
    mock.SetupGet(m => m.Property).Returns(3);
    mock.Object.Value = 4;
    Assert.Equal(3, mock.Object.Value);
}

Expected behavior:

Both tests should pass, since one would expect that the SetupGet setup will be invoked.

Actual behavior:

The second test fails.

Likely cause:

#826 made stubbed properties fully lazy. (Mock.Of<T> traditionally calls SetupAllProperties, which stubs all properties.) Setups for stubbed properties are now only added once that property is first queried (or being assigned to). That's the reason why the SetupGet setup in the second test gets overridden/shadowed by the stubbed property getter.

Possible resolution:

If we classify the new behavior (where the second test fails since 4.12.0) as a regression or bug, we could only register stubbed properties' lazy accessor setups if there isn't already a pre-existing setup for that accessor.

/cc @bluechrism @lepijohnny

@stakx stakx added the bug label Oct 6, 2020
@lepijohnny
Copy link
Contributor

lepijohnny commented Oct 8, 2020

Hi @stakx,

the proposal makes sense. However, I have 2 questions:

  1. Would the change be as simple as adding, and respective setter?
    if (ProxyFactory.Instance.IsMethodVisible(getter, out _) && !mock.MutableSetups.Any(setup => setup.Method.Equals(getter))) at https://github.com/moq/moq4/blob/master/src/Moq/Interception/InterceptionAspects.cs#L227

  2. The other option, although might be a bit hacky, is to add method AddStub to the SetupCollection and add stubs always to the end so they have less priority than normal setups... There is one unit test failing but it is questionable if it is a proper test, I think it is low risk to alter that one. (failing test)

@stakx
Copy link
Contributor Author

stakx commented Oct 8, 2020

@lepijohnny:

(2) is a no-go because we should be moving away from different setup types and towards a single unified setup type. (That's what my draft PR for Behavior-based setups is about.)

(1) looks about right, although it's possible that there are corner cases that might complicate things. I cannot remember from memory right now but we'd possibly have to consider at least (a) method overrides, (b) indexers, and (c) setups for set accessors with a fixed target value.

Re: (a) the possible issue is that .Method.Equals(...) might be too strict. Haven't thought about that in-depth yet.

Re: (b) and (c), the trouble might be us adding a very non-specific setup with It.IsAny<> for the parameters (target value or indexes), while more specific setups might already have been made; our less specific setup would then override the pre-existing, more specific one, which would be bad. The good news is that IIRC indexers likely aren't stubbed, so we might only have to deal with (c).

@lepijohnny
Copy link
Contributor

@stakx

(a) (b) the whole block is already wrapped by IsPropertyAccessor so don't expect the method or indexer to end up there at all.
(c) This should be covered by this test case? (currently failing)

[Fact]
public void Stubbed_property_set_with_fixed_target_value()
{
	var mock = Mock.Get(Mock.Of<IFoo>());
	mock.SetupSet(m => m.ValueProperty = 6);
	mock.Object.ValueProperty = 4;
	Assert.Equal(4, mock.Object.ValueProperty);
}

@stakx
Copy link
Contributor Author

stakx commented Oct 9, 2020

@lepijohnny – thanks for taking a closer look. Looks about right (except for the potential method override issue, we should perhaps have a test for that, too). Feel free to work on this, if you'd like.

@lepijohnny
Copy link
Contributor

@stakx,

Let me take a closer look to the ongoing PR about the behavioral setups(I like the idea btw).

I will come back with some proposals or dilemmas soon.

@aysan0
Copy link

aysan0 commented Jul 14, 2021

Hello, I believe I am facing this issue. Is there any advice on what to do? I use SetupAllProperties and have a SetupGet on a property (I'll call it propertyA). Somewhere in the code, the propertyA will be set, but when calling it at unit test, with version 4.10.1, since there was a SetupGet the set action was effectively ignored (I think). I tried setting up a SetupSet with no luck. New in moq, so would be happy with any feedback/challenging my assumptions.

Are there any alternatives to SetupAllProperties that would be suggested for me?

@stakx
Copy link
Contributor Author

stakx commented Aug 18, 2021

I've been looking into possible ways to fix this. Probably the cleanest approach would be to first refactor Moq such that a single setup can handle several different methods, and then introducing some new setup type StubbedPropertiesSetup that handles all property accessors, and keeps a dictionary representing all their "backing fields".

This would allow us to fix this issue, as well allow us quite a few bits of code spread everywhere that deals with lazy property stubbing.

(I said above that a new setup type would be a no-go. I've since tinkered some more with refactoring setups towards Behaviors, and it turns out that having different setup types likely won't be a problem after all.)

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

Successfully merging a pull request may close this issue.

3 participants