Skip to content

[RFC] Remove the code we have just for mocking#1272

Open
carlosmn wants to merge 4 commits intomasterfrom
cmn/unmock
Open

[RFC] Remove the code we have just for mocking#1272
carlosmn wants to merge 4 commits intomasterfrom
cmn/unmock

Conversation

@carlosmn
Copy link
Copy Markdown
Member

@carlosmn carlosmn commented Mar 7, 2016

We make the constructors internal but then make sure we cave
constructors with zero arity so mocking them via e.g. Moq is
easier. With this we're making out code harder to mock, but then add
ways to add another layer of mocking easier.

We enforce this constructors for mocking in the tests, without regard
for whether they make sense or not. Often it would make a lot more sense
to expose a public constructor for types so an application can return
fake results.


I'm thinking specifically that what you would want to do 90% of the time is mock your own app (if at all) and then return a fake ...Entry or ...Result type, and we can provide constructors for those so we treat them more like a struct than a class with actual scoping.

/cc @haacked and @shana for thoughts on mocking and whether something might actually break and what we can do to fix it.

We make the constructors internal but then make sure we cave
constructors with zero arity so mocking them via e.g. Moq is
easier. With this we're making out code harder to mock, but then add
ways to add another layer of mocking easier.

We enforce this constructors for mocking in the tests, without regard
for whether they make sense or not. Often it would make a lot more sense
to expose a public constructor for types so an application can return
fake results.
@haacked
Copy link
Copy Markdown
Contributor

haacked commented Mar 7, 2016

Often it would make a lot more sense
to expose a public constructor for types so an application can return
fake results.

Yeah, in many cases that's true. But looking at this change briefly, it looks like it just removes protected constructors without adding public constructors.

I'm thinking specifically that what you would want to do 90% of the time is mock your own app (if at all)

Yeah. Typically I'll have a method in my app that returns (or accepts) a LibGit2 object and I want to abstract the call to LibGit2 so I don't actually have to do a git operation. For example,

public int GetRepositoryIsAwesomeScore(LibGit2Sharp.Repository repository)
{
    // Does stuff with repository. Returns a score.
    return 100; // all my repos are awesome.
}

So now, I want to write a test of my method. If I can't instantiate or mock Repository I can't test it. I have to create my own wrapper for Repository and pass that in. I'd rather not create a wrapper for every class in LibGit2Sharp.

This is one reason I like the IRepository interface. I can just fake an implementation with just the properties I need set in order to test my method rather than instantiating a full instance.

In C#, as you know, classes are not sealed by default, but methods are. So a non-sealed class with only internal constructors is just the work. It offers the promise that I can override it, but I can't.

I would love it if every class implemented an interface (and the API returned the interfaces). Then I wouldn't care if every class had internal constructors or if the classes were sealed.

Does that make sense?

@ethomson
Copy link
Copy Markdown
Member

ethomson commented Mar 7, 2016

I understand that you don' t want to create a wrapper class for every LibGit2Sharp type. But isn't that roughly the same problem as creating an interface for every LibGit2Sharp type?

@haacked
Copy link
Copy Markdown
Contributor

haacked commented Mar 7, 2016

I understand that you don' t want to create a wrapper class for every LibGit2Sharp type. But isn't that roughly the same problem as creating an interface for every LibGit2Sharp type?

No, because there are great frameworks for dynamically implementing an interface. For example, suppose my method is:

public class RepositoryScorer {
  public int GetRepositoryIsAwesomeScore(LibGit2Sharp.IRepository repository) {
    return repository.Name == "haacked.com" ? 100 : 0;
  }
}

My test is

var fixture = new RepositoryScorerer();
var repo = Substitute.For<IRepository>();
repo.Name.Returns("haacked.com");

var result = fixture.GetRepositoryIsAwesomeScore(repo);

Assert.Equal(100, result);

Note that this is a super dumb simple example, but hopefully it conveys the point. I'm not interested in testing the internals of LibGit2Sharp, I assume you do that. 😄 I just want to test my interactions with LibGit2Sharp.

@ethomson
Copy link
Copy Markdown
Member

ethomson commented Mar 7, 2016

@haacked I guess I'm not up to speed on the state of the art when it comes to .NET mocking and dependency injection. I've found tools on other platforms (eg Mockito) to be reasonably capable of mocking concrete classes in the past.

Certainly we should not make it too difficult for consumers to mock us. But if this is a limitation of a particular DI library then I'm less excited about 1:1 interfaces:classes.

@haacked
Copy link
Copy Markdown
Contributor

haacked commented Mar 7, 2016

But if this is a limitation of a particular DI library then I'm less excited about 1:1 interfaces:classes.

It's not a limitation of a particular DI library, it's a limitation of the CLR and the design of LibGit2Sharp. I'll try and explain.

Mockito is for Java, right? In Java, methods are virtual by default. So mocking a typical class works because a typical class is not sealed. But if you seal a jar for example, Mockito will fail.

In .NET, it is possible to mock concrete classes if you can instantiate them and if the methods you're mocking are virtual. But if you make the constructor internal, I can't instantiate them without resorting to hacks such as private reflection. That's one of the reasons we advocated adding protected parameterless constructors. It lowers the difficulty in creating automatic mocks.

In theory, I could use the profiling API which basically hijacks the runtime and lets me override anything, but that's a heavyweight approach and is just working around a design that provides very little seams for testing.

@carlosmn
Copy link
Copy Markdown
Member Author

carlosmn commented Mar 7, 2016

The repository is a bit of a catch-all, so there's probably no way around mocking or having an interface, but if we make the results and status objects have a constructor so you can create arbitrary ones, you wouldn't need to mock whatever's creating them, since you can have whatever you're doing return that result without calling out to the library. I'm hoping this is enough for those classes which are are basically structs we have made classes.

The examples of mocking in C# I've seen all seem to be a mess, so if like to have a look at our usage to see if it matches what I'm thinking.

@haacked
Copy link
Copy Markdown
Contributor

haacked commented Mar 7, 2016

The repository is a bit of a catch-all

This term is overloaded so it's hard to tell if you're talking about this repository or the Repository class. 😛

so there's probably no way around mocking or having an interface

Yeah, which is why I'm very happy we have IRepository today.

but if we make the results and status objects have a constructor so you can create arbitrary ones, you wouldn't need to mock whatever's creating them, since you can have whatever you're doing return that result without calling out to the library

Yeah, at the end of the day, if I can create the object without having to use private reflection, then I'm happy. This isn't true for all cases. For example, some objects are hard to construct and require building whole object graphs which can get annoying when I really just want to isolate a slice of the object.

I'm hoping this is enough for those classes which are are basically structs we have made classes.

Yeah, that makes sense. Again, the main problem right now is that I can't instantiate them with the values I want. With a typical struct, I can do that. That's why I'm not going around asking people to add interfaces for their structs. 😄

@carlosmn
Copy link
Copy Markdown
Member Author

carlosmn commented Mar 7, 2016

I did mean the Repository class :)

When you need a whole graph of objects, then that does get trickier, but my
idea is indeed to let you create your own simple objects when you just want
some answer from the library like the state of the Repository.
On Mar 7, 2016 18:11, "Phil Haack" notifications@github.com wrote:

The repository is a bit of a catch-all

This term is overloaded so it's hard to tell if you're talking about this
repository or the Repository class. [image: 😛]

so there's probably no way around mocking or having an interface

Yeah, which is why I'm very happy we have IRepository
https://github.com/libgit2/libgit2sharp/blob/master/LibGit2Sharp/IRepository.cs
today.

but if we make the results and status objects have a constructor so you
can create arbitrary ones, you wouldn't need to mock whatever's creating
them, since you can have whatever you're doing return that result without
calling out to the library

Yeah, at the end of the day, if I can create the object without having to
use private reflection, then I'm happy. This isn't true for all cases. For
example, some objects are hard to construct and require building whole
object graphs which can get annoying when I really just want to isolate a
slice of the object.

I'm hoping this is enough for those classes which are are basically
structs we have made classes.

Yeah, that makes sense. Again, the main problem right now is that I can't
instantiate them with the values I want. With a typical struct, I can do
that. That's why I'm not going around asking people to add interfaces for
their structs. [image: 😄]


Reply to this email directly or view it on GitHub
#1272 (comment)
.

@haacked
Copy link
Copy Markdown
Contributor

haacked commented Mar 7, 2016

When you need a whole graph of objects, then that does get trickier, but my
idea is indeed to let you create your own simple objects when you just want
some answer from the library like the state of the Repository.

Cool. I think that'll help, but I'll have to see examples. As you can imagine, GitHub Desktop's use case is a bit more involved than most where we often need to explore the structure of the IRepository instance. But helper methods for the common types of things we need to query the repo for would help.

carlosmn added 2 commits March 8, 2016 10:32
Instead of hiding everything but making it virtual for mocking, let the
users create their own objects with the data they want.
@carlosmn
Copy link
Copy Markdown
Member Author

carlosmn commented Mar 8, 2016

I've made anything Result or Entry have settable fields or a public constructor. Most could just be converted to structs and remove the virtual (which I understand is there just for the mocking), which would also let us put this stuff on the stack more often. I'm not sure how much that would break existing use however, or whether there's a way to deprecate them well.

@carlosmn
Copy link
Copy Markdown
Member Author

My next step would be to fold the RepositoryExtensions into the Repository class itself and unseal it. This would allow you to inherit from Repository and together with making the result types usable by the callers, you can return whatever you want during your tests.

We should not have extensions on our own types. Either we accept these
convenience overloads as part of our interfaces or we shouldn't have
them.

While here unseal Repository and split up these methods into a few
different files grouping them by area.
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

Successfully merging this pull request may close these issues.

3 participants