Here is a preview of some use cases from the new Typemock Isolator API coming out sometime in August. I have some questions for you which I’d love for you to answer in the comments.
First, a little background on the new API that should come out sometime in august.
- The new Isolator API goes in the direction of AAA – Arrange,Act,Assert and backs away from the more traditional Record-Replay model.
- No mocks or stubs. Everything is defaulted to a fake object, which can either be used as a stub or as a mock object later on. If at the end of the test you call “Verify” on that object, that it is logically a mock. if you tell that object to return specific values or throw exceptions during the test, it is a stub. but the API does not mention these words for clarity.
- No strings (Except for non public members)
- There is a single point of entry to the API, currently called “MyTest” which is used to create all the types of fakes.
- There are several categories of faking which can be done, and they are represented in the MyTest as properties: “Make”, “Static”, “Swap”,”NonPublic”.
- Make – Creates instances of fake objects to be used later on in the test. can be interfaces or real classes.
- Static – used for faking out static methods or constructors
- Swap: used for replacing objects that will be created in the future with fake objects
- NonPublic – used as the entry point for faking out anything that is not accessible via publicly accessible apis.
Let’s see some use cases:
Use case #1: fake
A simple fake that will be used as a stub(all stubs are non strict by default – you can call any method that is void without getting an exception):
ILogger fakeLogger = MyTest.Fake.Instance<ILogger>(); //arrange
MyTest.WhenCalled(() => fake.Log(“s”)).WillReturn(false);
new SomeObject(fakeLogger).DoSomething();//act
Assert.IsTrue(….) //assert
Here is the same use case with the fake logger used as a mock to check if it was called:
#2 verify on fake:
ILogger fakeLogger = MyTest.Fake.Instance<ILogger>(); //arrange
new SomeObject(fakeLogger).DoSomething(); //act
//assert: next three are possible
MyTest.Verify.WasCalledWithAnyArguments(() => fake.Log(“”)); /// passed arguments are ignored
MyTest.Verify.WasCalledWithExactArguments(() => logger.Log(“a”)); ///matching according to passed arguments
MyTest.Verify.WasNotCalled(() => logger.Log(“”));
The API above will also work with real types, not just interfaces. Here is an example of create a type that calls the original methods and only some are overridden (partial mocks):
#3 partial fakes:
RealLogger real = MyTest.Fake.Instance<RealLogger>(Members.CallOriginalMembers); //arrange
MyTest.WhenCalled(() => real.Log(“s”)).WillReturn(false);//all other methods work on the real object except this one
new SomeObject(real).DoSomething();
Here is how we handle static methods: per method, on all static methods, and on the static constructor
#4 – statics:
MyTest.Static.WhenCalled(() => Logger.Log(“s”)).WillReturn(false);
MyTest.Static.WhenCalled(() => Logger.Log(“s”)).IgnoreCall();
MyTest.Static.FakeStaticMethods<RealLogger>();
MyTest.Static.FakeStaticConstructor<RealLogger>(StaticFields.AreNull); //or StaticFields.InitializedToDefaultValues
Isolator also allows setting behavior on objects that will be created in the future. for example, in your code under test someone creates a new Logger and calls write on it. Also it allows taking over (sapping) on all current and future instances of a type.
#5 – Future objects
RealLogger fake = MyTest.Fake.Instance<RealLogger>();
MyTest.WhenCalled(() => fake.Log(“s”)).WillReturn(false)
MyTest.Swap.FutureInstance(fake).WhenCalled(() => new RealLogger(“ctor arg”));
MyTest.Swap.AllCurrentAndFutureInstances<RealLogger>(fake);
Isolator is able fake privates easily, but requires strings to do this. We are not sure how it will look. Here are several possible ways. your input can help us decide.
#7 – Non public members
RealLogger fake = MyTest.Fake.Instance<RealLogger>();
MyTest.NonPublic.WhenCalled(logger,”SomePrivateMethod”).WillThrow(…) //option 1. If you put the name of a public method in the string you get an exception
MyTest.StringBased.WhenCalled(logger,”SomePrivateMethod”).WillThrow(…) //option 2 – any method name will work public or private.
Isolator is able take an instance of an existing object
#8 – Existing Instances (live objects)
RealLogger log = new RealLogger(…);
MyTest.Fake.ExistingInstance(log)
MyTest.WhenCalled(() => real.Log(“s”)).WillReturn(false);
There are the basics. In future posts I will dive a little deeper into more features (custom argument checking, custom callbacks and more).
Your thoughts and comments would be highly appreciated!
Question: What are our thoughts on the NonPublic dillemma?
- Also see Eli’s post on this
- Should it throw is we send it something which is public and instruct user to use the strongly typed version of this? (can make tests more brittle if user refactors to make something from private to public)
- should we name it NonPublic or something else? (“StringBased” for example)