Skip to content

Commit

Permalink
Test detection accuracy of non-transitive calls
Browse files Browse the repository at this point in the history
The current implementation of `VerifyNoOtherCalls` has one short-
coming, which is that if a method is invoked in both a "transitive"
manner but also non-transitively, then the latter cannot be recognized
as such. For example:

    var mock = new Mock<IFoo> { DefaultValue = DefaultValue.Mock };

    mock.Object.A().B();
    mock.Object.A();

    mock.Verify(m => m.A().B());
    mock.VerifyNoOtherCalls();

With the above, `VerifyNoOtherCalls` won't currently be able to recog-
nise the second, "non-transitive" call to `.A()` as one that needs to
be explicitly verified. No verification exception will be thrown.

Add tests that document both this problem, and a possible way of fix-
ing it.
  • Loading branch information
stakx committed Dec 4, 2017
1 parent ed1d137 commit 6cac284
Showing 1 changed file with 41 additions and 0 deletions.
41 changes: 41 additions & 0 deletions Moq.Tests/VerifyFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1269,6 +1269,47 @@ public void VerifyNoOtherCalls_performs_recursive_verification()
Assert.Throws<MockException>(() => mock.VerifyNoOtherCalls()); // should fail due to the unverified call to `Poke`
}

[Fact]
public void VerifyNoOtherCalls_requires_explicit_verification_of_automocked_properties_that_are_not_used_transitively()
{
var mock = new Mock<IFoo>() { DefaultValue = DefaultValue.Mock };

var _ = mock.Object.Bar;

// Even though `Bar` is mockable and will be automatically mocked, it isn't used "transitively",
// i.e. in a way to get at a sub-property. Therefore, it should have to be explicitly verified.
// When it isn't, a verification exception should be thrown:
Assert.Throws<MockException>(() => mock.VerifyNoOtherCalls());
}

[Fact(Skip = "Not yet implemented.")]
public void VerifyNoOtherCalls_can_tell_apart_transitive_and_nontransitive_usages_of_automocked_properties()
{
var mock = new Mock<IFoo>() { DefaultValue = DefaultValue.Mock };

object _;
_ = mock.Object.Bar;
_ = mock.Object.Bar.Value;

mock.Verify(m => m.Bar.Value);

// `Bar` was used both in a "transitive" and non-transitive way. We would expect that the former
// doesn't have to be explicitly verified (as it's implied by the verifiation of `Bar.Value`).
// However, the non-transitive usage should have to be explicitly verified. When we don't,
// a verification exception should be thrown. Unfortunately, THIS CURRENTLY DOES NOT WORK:
Assert.Throws<MockException>(() => mock.VerifyNoOtherCalls());

// HINT TO IMPLEMENTERS: One relatively easy way to implement this, given the current architecture
// of Moq, would be to record all invocations with a globally unique, steadily increasing sequence
// number. This would make it possible to judge, for any two invocations (regardless on what mock
// object they occurred), which happened earlier. Let's look at two invocations of method X. The
// earlier invocations happened at "time" t_a, the later invocation happened at "time" t_b. If
// X returns a (inner) mock object, and that object has no invocations happening at a time t_x
// where t_a < t_x < t_b, then the first invocation of X was non-transitive. Likewise, for the
// very last invocation of method X, it is non-transitive if there are no invocations on the
// inner mock returned by it having a later "time".
}

public interface IBar
{
int? Value { get; set; }
Expand Down

0 comments on commit 6cac284

Please sign in to comment.