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

Use Moq as a proxy #1287

Closed
apazureck opened this issue Sep 7, 2022 · 5 comments
Closed

Use Moq as a proxy #1287

apazureck opened this issue Sep 7, 2022 · 5 comments

Comments

@apazureck
Copy link

Hi,

I have a question / feature request (?) using Moq as a proxy and just mocking / "rerouting" a specific method / property.

So what I want to do:

I am using the Docker Dotnet Library and want to use it in my Integration / Component tests. As Docker is running locally I would just use the real Docker for most of the tests (Creating Containers, Creating Images, etc.). But I would like to replace some parts of the default behavior using the Moq Syntax, to simulate errors.

In my actual spec I need to check, if the docker API is capable of using the GPU.

So a minimal Setup and Usage could look like this:

Remark: This is not the actual docker dotnet API structure. I minimized it for illustration of what I would like to pull off.

// Create a mock, which will forward all methods and properties to the underlying client
var actualDockerClient = new DockerClientConfiguration().CreateClient();
var dockerClientProxyMock = ProxyMock.Of<IDockerClient>(actualDockerClient);

// Override one specific behavior
dockerClientProxyMock.Setup(dc => dc.GetVersion()).Returns(new VersionResponse() { ApiVersion = "1.4.0" });

// Returns the mocked version, as moq will use the intercepted method
var versionResponse = dockerClientProxyMock.Object.GetVersionAsync();

// Uses actually docker to create the container.
var actuallyCreatedContainer = dockerClientProxyMock.Object.CreateContainer(imageName: "hello-world");

I know I could use Castle to do that, too. But I thought it would be nice to have the commonly known Moq Syntax for my team.

I read the article here, but it did not work using an interface for me so far:

https://blog.wiseowls.co.nz/index.php/2018/11/14/moq-ing-around-existing-instance/

Thanks in advance for your help!

@stakx
Copy link
Contributor

stakx commented Oct 5, 2022

DynamicProxy would definitely be the better tool to use for this purpose.

Moq doesn't support composition-based proxying like DynamicProxy does, and that is unlikely to change. The only real option you've got when using Moq is to mock some DockerClient class implementing IDockerClient instead of the latter interface, then setting mock.CallBase = true... but you'll only be able to setup those methods of DockerClient that are overridable.

If none of the above works for you, you may have to write your IDockerClient proxy by hand.

@stakx
Copy link
Contributor

stakx commented Dec 12, 2022

Closing this issue due to inactivity. If there's anything else you'd like to add regarding this, please do post again.

@stakx stakx closed this as completed Dec 12, 2022
@jeme
Copy link

jeme commented Jan 31, 2023

Just to add some additional info.

I ran into wanting something similar. But where we had some data models that are rather big, they implement a visitor pattern and during a routine they would be called with a specific visitor.

In order to test this we would mock the objects, however since they are somewhat big it would require a large amount of setups, instead what we could do is simply wrap them into a Mock and then verify the call to accept on that mock after.

So we have something like (shortened greatly here):

public static Mock<IOurData> ProxyMock(IOurData data) {
  Mock<IOurData> mock = new();
  mock.SetupGet(x => x.Foo).Returns(() => data.Foo);
  ... more gets
  mock.Setup(x => x.Accept(It.IsAny<IVisitor>())).Callback<IVisitor>(value.Accept);
  ... more methods
  return mock;
}

Test:

Mock<IOurData> mock = ProxyMock(TestDataFactory.CreateOurData());

... run test.

mock.Verify(x => x.Accept(It.Is<IVisitor>(v => v is ConcreteVisitor)));

But setting that up by hand can be somewhat tedious so it would be nice if it could somehow be done in a generic way or if Moq was easier to extend with such behavior.

@stakx
Copy link
Contributor

stakx commented Jan 31, 2023

I'll say it again, you'd likely be better off using DynamicProxy instead of Moq, if you need proxies.

@jeme
Copy link

jeme commented Feb 1, 2023

@stakx I did read that!

Please notice that there is a "Verify" call in the example. And likewise, we have cases where we would have some "overritten" Setup calls, so we are very much using Moq features on top of this, so to me it's not about proxying but more about what the "Default Behavior" should be.

If we build all that on top of DynamicProxy instead, then what do we really need Moq for anymore? So to me, your suggestion effectively is to build our own mocking framework based on DynamicProxy that supports defaulting its behavior to an underlying proxied object. And sure we can do that, but I don't feel that is the right spirit, I would rather improve Moq so we can all benefit from it.

I would be very happy if a solution would be found where a Mock could be Setup via "reflection" or something else more dynamic instead of the Expressions based API that there is today, this does not in any way have to be the responsibility of Moq, in fact I think it would be better as an extension as it's certainly a complex and difficult task to undertake.

IF that is already possible, then if you would be so kind, you could perhaps point me in the right direction. https://moq.github.io/moq4/ is mostly empty and just points back to the Quickstart which does not cover such advanced use cases, nor should it IMO so that is perfectly fine. And as far as I can see, a huge amount of things are keept as internal as pointed out by the blog in the initial post.

I would also not mind to assist if it (as I assume) requires some changes in MoQ. But you seem very closed off on the idea as a whole which I feel is a shame.

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

No branches or pull requests

3 participants