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

[Feature Request] Allow to change MockBehavior of mocks #1230

Closed
alex-fomin opened this issue Feb 4, 2022 · 15 comments
Closed

[Feature Request] Allow to change MockBehavior of mocks #1230

alex-fomin opened this issue Feb 4, 2022 · 15 comments

Comments

@alex-fomin
Copy link

This feature is especially useful with mix of AutoMocker. For instance, I have a complex class to test, so I setup a AutoMocker with Loose behavior and set all required dependencies (leaving all unimportant services as Loose mocks). But then I need to verify that particular service should be mocked as Strict to ensure that my test setups all required methods of the service. At the moment, I have to turn AutoMocker to Strict mode all together and provide all mocks with default behavior even if I'm not interested in them.
It would be cool to change behavior of mocks on the fly by changing its Behavior property.

@stakx
Copy link
Contributor

stakx commented Feb 12, 2022

Without knowing all the details of your exact testing scenario, why don't you simply use different mocks for the different test cases, some being strict, and some being loose? If the issue is having to set up several mocks almost identically, that should be easy enough to solve through a mock factory method having a MockBehavior parameter.

@alex-fomin
Copy link
Author

I'm using AutoMocker to populate complex objects with mocks. It has a constructor parameter MockBehavior that is used to create all mocks inside the autoMocker instance. As far there is no possibility to change mock behavior of already created mock, it is not convenient to setup complex objects mocking.

@stakx
Copy link
Contributor

stakx commented Feb 18, 2022

So if I understand correctly, the source of your problem is that AutoMocker only gives you the choice between loose and strict mocks once, when you'd really need to choose between those two behaviors at a more granular level? In that case, it seems you should make your request with AutoMocker instead... solve the problem at the source, instead of asking for a workaround further downstream.

@voroninp
Copy link

@stakx , do you remember our discussion about mixed mode: strict for queries, loose for commands? ;-)

I guess, @alex-fomin , needs something similar.

@alex-fomin
Copy link
Author

It's quite not the same. My example looks like

class ObjectUnderTest
{
  public ObjectUnderTest(IDependecy1 d1, IDependecy2 d2)
  { 
   // ...
  }
  
  public void MethodToTest(int arg)
  { 
     d1.NotImportantCall(...);
     d2.ImportantCall(...);
  }  
}


public void Test()
{
    var mocker = new AutoMocker(MockBehavior.Strict);

    mocker.GetMock<IDependecy2>()
      .Setup(x => x.ImportantCall(...)) // <- I want to be specific that IDependecy2 instance will be called as defined
      ...; // And neither additional calls, nor call with other parameters
 
    // I don't care about IDependecy2 calls at all
   mocker.GetMock<IDependency1>().Behavior = MockBehavior.Loose; // <- but I can't
   
   // But I will do care about new dependencies in MethodToTest in future
   // So I create AutoMocker with MockBehavior.Strict
   // So the test will fail in future after someone changes MethodToTest

    var service = mocker.Create<ObjectUnderTest>();

   service.MethodToTest(1);
   ...
}

@voroninp
Copy link

But why not call Automocker.Use method and pass loose mock for service d1?

Also, I suspect the method you don't care about either has command semantics, or it's output not being checked by the code which uses that value as an input.

@alex-fomin
Copy link
Author

In simple cases Use is quite enough, but when tests are more complicated, some calls/checks could be set in common methods of the test class (yeah, it is not a good pattern though).
As for non-cared methods - it could be either commands, like logs or so, or queries, when default results (e.g. null) are good enough (caches, etc.)

@stakx
Copy link
Contributor

stakx commented May 13, 2022

This still strikes me as a primarily AutoMocker-specific issue. Instead of changing Moq such that one can arbitrarily change an existing Mock<>'s behavior, AutoMocker should perhaps provide an API that allows you to override the MockBehavior on a per-request level.

Or, at least, AutoMocker, being the downstream library, should be the first to allow reconfiguring its MockBehavior. Having that ability in Moq, but not in AutoMocker, would force you to circumvent the abstractions that the latter provides, which doesn't strike me as a very clean solution.

While changing Moq as requested would be technically possible, I think it's the wrong approach to solve the issue at hand, and finally, I don't think it would be a particularly good enhancement; it could also make future maintenance more difficult.

@stakx stakx added the wontfix label May 13, 2022
@stakx stakx closed this as completed May 13, 2022
@stakx stakx removed the wontfix label May 13, 2022
@stakx
Copy link
Contributor

stakx commented May 13, 2022

(P.S.: removing the wontfix label because that seems a tad too final. It's possible that sometime in the future, we might identify another use case that's common enough to justify making MockBehavior mutable; but this one here isn't one of them.)

@dilandau2001
Copy link

Another case I am right now in that and I think it could be a valid scenario for the feature request.

using Xunit, with Autofixture and automoq.
I leave the Automoq feature do its job to create all dependencies of a tested class.

Lets say the test is something like this:

`
[Theory]
[AutoDomainData]
public async Task WhenThen([Frozen] Mock<ILogger> logger, Client sut)

// ILogger is the logging interface from base using Microsoft.Extensions.Logging;
// sut is the class under test that makes use of the logger.

`

ILogger interface is very basic and it has some extensions from Microsoft.
I am using Xunit so I am trying to redirect all Logging calls to the Xunit test output.
For that I have created, (trying to) create a static function that setups all possible calls to ILogger function to the ITestOutputHelper. (you can consider it tracing to console)
As extensions methods cannot be moq. The recommendation (as I found so far) is to configure the mock to be strict and do the setups over the base class. (Link )

Lets say this configuration is something like this:

`

public static void ConfigureLogger(Mock<ILogger> logger, ITestOutputHelper outputHelper)

`

As logger behaviour cannot be set, this way is not possible.

My only approach so far is to create the mock manually or to force autofixture to create all mocks with strict behaviour. Neither of both are ideal.

@voroninp
Copy link

@dilandau2001 , Why not just create custom ILogger<T> and factory for xUnit without bothering with Moq? Do you also need to verify certain calls?

@dilandau2001
Copy link

@voroninp
How do you tell Autofixture.Automoq to use your version of ILogger when autoinjecting atributes of the constructor?

@voroninp
Copy link

@dilandau2001 , custom resolver?

Here's a similar issue

@dilandau2001
Copy link

mmm, I wasn't using Moq.AutoMocker.
I was actually using Autofixture, and Autofixture attributes:
https://autofixture.github.io/docs/quick-start/#
here it is explained how to create a custom attribute that does all the work to create all mocks.

I feel that your suggestion should work somehow... although I don't know yet how I would add that resolver to the autofixture resolve pipeline.

@dilandau2001
Copy link

Just for the sake of continuation if someone falls into this post.
I have created a full post inside Autofixture github explaining my test case and why I would have needed this feature.
#AutoFixture/AutoFixture#1369

As it is kind of mix responsability among Autofixture, Xunit and Moq libraries I kind of feel the Autofixture github is the appropiate one.
Thanks for the suggestions and any other suggestion is highly appreciated.

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

No branches or pull requests

4 participants