-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
[RFC] IJsonSerializer parameter in Connection.cs wasn't being used #1133
Conversation
… before. Breaks tests.
The failing tests seem to be due to the fact that they pass in |
Yes, in fact it is because of it. I tried passing an actual object of Can we pass an actual object, instead of a substitute? |
I'm ok with whatever you want to do. Can you tell me where should I look into to learn further more about this? |
I'm honestly not sure myself... |
I don't think we can break at one condition, but if we break down the compound |
That gave me an idea, I'll get back to you in a bit. |
TL;DR use the substitute also for setting up the data. var serializer = Substitute.For<IJsonSerializer>();
string data = serializer.Serialize(new object());
var httpClient = Substitute.For<IHttpClient>();
IResponse response = new Response();
httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response));
var connection = new Connection(new ProductHeaderValue("OctokitTests"),
_exampleUri,
Substitute.For<ICredentialStore>(),
httpClient,
serializer); |
It is a clever trick, but due to lack of experience, I can't say whether we should use this or not. @shiftkey, feedback? |
Hey @devkhan thanks for contributing the fix and this is a great opportunity for you to gain experience in the area of open source contributions and familiarity with mock based unit testing! :) The purpose of the Octokit.net unit tests is to verify expected behaviour but run totally self contained locally (ie not calling out to any github.com api calls and the like). So the Mocks help us achieve the Unit Testing "Arrange, Act, Assert" pattern.
So anyway, the reason those tests are now failing is that the substituted @M-Zuber 's example is setting the expected body data to be the (null) return of the mocked serializer... which does make the test pass, however I would probably recommend instead to configure the mock to return the data we want since that is more in keeping with the unit testing approach and would also be applicable in other situations where we had a "real" body instead of a We can also add an assert to ensure that the Does that give you enough information @devkhan to tackle correcting the failing unit tests yourself? To give an example of how I would probably fix these unit tests (explanatory comments like this aren't required, im just adding them to walk you through what's happening): [Fact]
public async Task RunsConfiguredAppWithAppropriateEnv()
{
// Arrange
// - Setup request body and expected data
var body = new object();
string expectedData = SimpleJson.SerializeObject(body);
// - Setup IJsonSerializer mock to return expectedData when Serialize() is called
var serializer = Substitute.For<IJsonSerializer>();
serializer.Serialize(body)
.Returns(expectedData);
// - Setup IHttpClient mock to return a response object when Send() is called
IResponse response = new Response();
var httpClient = Substitute.For<IHttpClient>();
httpClient.Send(Args.Request, Args.CancellationToken)
.Returns(Task.FromResult(response));
// Act
// - Create our connection object
var connection = new Connection(new ProductHeaderValue("OctokitTests"),
_exampleUri,
Substitute.For<ICredentialStore>(),
httpClient,
serializer);
// - Make the Patch() call
await connection.Patch<string>(new Uri("endpoint", UriKind.Relative), body);
// Assert
// - Verify JsonSerializer.serialize() was called with body object
serializer.Received(1).Serialize(body);
// - Verify HttpClient.Send() was called with all the correct arguments
httpClient.Received(1).Send(Arg.Is<IRequest>(req =>
req.BaseAddress == _exampleUri &&
(string)req.Body == expectedData &&
req.Method == HttpVerb.Patch &&
req.ContentType == "application/x-www-form-urlencoded" &&
req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken);
} |
To follow up on @ryangribble's summary, if they're not testing the actual serialized result (which I'm pretty sure is most of them) we can just simplify things to indicate that we don't care. For example: [Fact]
public async Task RunsConfiguredAppWithAppropriateEnv()
{
var httpClient = Substitute.For<IHttpClient>();
IResponse response = new Response();
httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response));
var connection = new Connection(new ProductHeaderValue("OctokitTests"),
_exampleUri,
Substitute.For<ICredentialStore>(),
httpClient,
Substitute.For<IJsonSerializer>());
await connection.Patch<string>(new Uri("endpoint", UriKind.Relative), new object());
httpClient.Received(1).Send(Arg.Is<IRequest>(req =>
req.BaseAddress == _exampleUri &&
req.Body != null &&
req.Method == HttpVerb.Patch &&
req.ContentType == "application/x-www-form-urlencoded" &&
req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken);
} Or you could drop the |
@ryangribble thanks for explaining 🙇 It did filled some gaps in my knowledge. And, now that I understand the project better, I can tackle the problem at hand. @shiftkey thanks. I will surely first try to keep the correctness intact as much as possible. Let me get back to you. Just to be sure: I should try to configure the object's substitute in a way that |
@ryangribble I tried to change the substitute, but the point is Anything else should I add? |
Personally I do prefer to either do like I did and declare the body and the expected serialised data (even if blank) or do like @shiftkey and indicate you don't care to check the body at all Anyway, just give it a go however you think and push up your changes and we can have a look |
I was very tired last night and not paying attention to the point raised by @ryangribble , namely that no mock was set for the Personally I would mock that call and test that body == data in order to not leave holes in the test. You can not always know what edge case will be protected against by having that in the test. |
I have applied @M-Zuber 's fix to the failing tests. Now the question is, how to better mock the request body? |
I tried substituting the Substitute.For<IObject>().ToString().Returns(""); // I defined a dummy IObject. :P which does passes the test, but as @ryangribble said, I don't how it will provide checking mechanism for other types of request (as this is a simple Patch request, we are simply passing in a plain How should I mock the |
You shouldn't need to mock the object being passed in. var serializer = Substitute.For<IJsonSerializer>();
serializer.Serialize(body)
.Returns(SimpleJson.SerializeObject(body)); which is setting up the mock Warning I do not know if the above works, but I believe that is at least the correct concept. |
But then again won't it be the same as before, when directly P.S.: When I tried it, the tests were failing. 😢 |
does the test I posted earlier work for you? what about the variant @shiftkey posted? |
@shiftkey's variant works , but not yours |
I currently don't have access to my system, so I'll make the changes later, but it should work just fine. |
@M-Zuber that sounds like you dont actually have the "fix" from this PR in place? It also shows that my test asserts the Serializer passed in is actually used, whilst the other tests didnt actually check this. Whether this is something these particular tests SHOULD test (or whether it should be covered off by another test) is up for debate though :) |
Martin Fowler has a great post about using mocks and stubs. http://www.martinfowler.com/articles/mocksArentStubs.html In general, I prefer state based testing over interaction testing. With Octokit.net, sometimes that's really difficult by the nature of what we're doing. But in this case, I'd be fine with passing the actual seralizer because it's return value is deterministic. However, I'm also fine with the format that @ryangribble and @shiftkey proposed. |
It is deterministic because we are passing |
@ryangribble - hanging my head. I am going to sleep for a month or so and then I'll come back :) |
@M-Zuber Did I made a mistake? |
No, I wasn't paying enough attention and reporting false findings, thats all. |
Hehe, we've all been there. Pretty funny you did all the right stuff on the tests, but forgot to actually implement the fix that started the entire PR! 😆 |
Thanks for your work @devkhan and good discussion by all :) |
[RFC] [WIP] IJsonSerializer parameter in Connection.cs wasn't being used
Thanks everyone. @M-Zuber @ryangribble @haacked @shiftkey |
Passing on serialize argument to Connection's ctor, wasn't passing on before.
Breaks testsUpdated tests.Cannot mergeMerged.Refer #1119
This breaks six tests in total, one of them being this, I guess that's because, the
IJsonSerializer
in the test is a substitute, it doesn't actually provide a serialization strategy. I don't know much about unit testing and mocking frameworks.@shiftkey @M-Zuber thoughts?