简体   繁体   中英

NSubstitute auto-mocking certain types only

I've noticed that NSubstitute auto-mocks the following types:

  • Array
  • IObservable
  • String
  • Task

Although I know this happens, I can't find the reason for picking these types. Why not leave String null? Or include collections instead of just Array ?

For example:

var crazyInterface = Substitute.For<ICrazyInterface>();

FileInfo[] folderContents = crazyInterface.FolderContents(@"C:\folder");
IObservable<FileInfo> fileObserver = crazyInterface.CreateNewFileObservable();
string fileHash = crazyInterface.FileHash(@"C:\folder\file.txt");
Task<byte[]> fileContents = crazyInterface.ReadFileContentsAsync(@"C:\folder\file.txt");

All get a mock implemented for them, whereas List<T> would be null (or any other reference type).

The source code gives no indication as to the reason just implements to auto-mocks in the AutoValues folder.

Short answer: specific auto-mocked types just got added as people wanted them and we felt the convenience would outweigh any confusion caused by returning a real value when a mock or default(T) may have been expected.


In case anyone's interested here's the longer, hopefully-partially-accurate story of this feature's evolution as I recall it.

NSubstitute started off returning default(T) for everything. This gets painful for cases where we have chains of calls, so we added auto-mocking for interface types. This is a fairly safe operation - unlike with classes there is no potential for real code to run, say, from the constructor call, or by inadvertently calling a non-virtual method. For example, mySub.SomeAutoSub().DoStuff() could run real code or not based on the return type of SomeAutoSub() .

The next level of safety is classes that have all virtual members, and a default constructor. The documentation refers to theses as pure virtual classes . Having virtual members means if we dig in to an auto-subbed instance we can't accidentally call real code. Now this will still run real code via the constructor, but it doesn't take parameters and so we can assume (read: cross-fingers and hope) the default behaviour will do something sensible rather than doing anything terrible.

Finally, there are the exceptional cases you pointed out like Array and Task . These were added completely ad hoc based on feedback when we felt the convenience would outweigh any confusion caused. The main thing we want to avoid with these is returning a real value when a mock was expected. NSubstitute doesn't work well if you accidentally call .Returns on something that isn't a mock.

Strings , Arrays and Tasks meet this criteria. You can't mock these types so having a default value that works sensibly in tests seems reasonable. I found I often ran into string ops that blew up because of the null default, so I added that one for convenience. Others wanted a default Task that worked sensibly and so added that. I can't remember the reasoning behind not auto-subbing collections (possibly because some people use mocked versions of IList<T> , where providing a specific implementation like List<T> is easier enough to explicitly stub?).

I think we can get away with some inconsistency here because we are in the context of tests -- if we need a specific value or behaviour we'll explicitly stub it out. Otherwise we'll get a default value that hopefully won't get in the way.

If you would like additional things auto-subbed please ping the discussion group . It's a bit harder to make potentially-breaking changes like this now but if there's a compelling case for a particular type we can look at adding it in.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM