可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm really new to mocks and am trying to replace a private field with a mock object. Currently the instance of the private field is created in the constructor. My code looks like...
public class Cache { private ISnapshot _lastest_snapshot; public ISnapshot LatestSnapshot { get { return this._lastest_snapshot; } private set { this._latest_snapshot = value; } } public Cache() { this.LatestSnapshot = new Snapshot(); } public void Freeze(IUpdates Updates) { ISnapshot _next = this.LastestSnapshot.CreateNext(); _next.FreezeFrom(Updates); this.LastestSnapshot = _next; } }
What I'm trying to do is create a unit test that asserts ISnapshot.FreezeFrom(IUpdates)
is called from within Cache.Freeze(IUpdates)
. I'm guessing I should replace the private field _latest_snapshot
with a mock object (maybe wrong assumption?). How would I go about that while still retaining a parameterless constructor and not resorting to making LatestSnapshot
's set public?
If I'm totally going about writing the test the wrong way then please do point out as well.
The actual implementation of ISnapshot.FreezeFrom
itself calls a heirarchy of other methods with a deep object graph so I'm not too keen on asserting the object graph.
Thanks in advance.
回答1:
I'm almost citing techniques from "Working Effectively with Legacy Code":
- Sub-class your class in a unit test and supersede your private variable with a mock object in it (by adding a public setter or in the constructor). You probably have to make the variable protected.
- Make a protected getter for this private variable, and override it in testing subclass to return a mock object instead of the actual private variable.
- Create a protected factory method for creating
ISnapshot
object, and override it in testing subclass to return an instance of a mock object instead of the real one. This way the constructor will get the right value from the start. - Parametrize constructor to take an instance of
ISnapshot
.
回答2:
I don't think you'd need to mock private member variables. Isn't the whole idea of mocking that the public interface for an object works as expected? Private variables are implementation details that mocks aren't concerned with.
回答3:
I'm not sure that you can do that. If you're wanting to test _next
then you're probably going to have to pass it in as a parameter and then in your unit test pass in a Mock object which you can then test using an Expectation. That's what I'd be doing if I were trying to do it in Moq.
As an example of what I might try using the Moq framework:
Mock<ISnapshot> snapshotMock = new Mock<ISnapshot>(); snapshotMock.Expect(p => p.FreezeFrom(expectedUpdate)).AtMostOnce(); Cache c = new Cache(snapshotMock.Object); c.Freeze(expectedUpdate);
Note: I haven't tried to compile the above code. Its just there to give an example of how I'd approach solving this.
回答4:
This answer might be simple, but looking at the code, is there any way in which ISnapshot.FreezeFrom(IUpdates)
is not going to be called? Sounds like you want to assert something that will always be true.
As Jason says, mocking is meant for situations where your class depends on SomeInterface
to do it's work, and you want to test YourClass
in isolation from whichever implementation of SomeInterface
you actually use at runtime.
回答5:
The question to ask is: what are the externally visible effects if this worked?
What happens to all those Snapshots? One option might to initialise the Cache with its first Snapshot from outside, say in the constructor. Another might be to mock whatever it is that the Snapshot calls that matters outside the cache. It depends on what you care that happens.
回答6:
It might be too late to respond. Anyways. I also had similar problem.
public class Model { public ISomeClass XYZ{ get; private set; } }
I required to set value of XYZ in my test case. I resolved the problem using this syntex.
Expect.Call(_model.XYZ).Return(new SomeClass()); _repository.ReplayAll();
In the case above we can do it like this
Expect.Call(_cache.LatestSnapshot).Return(new Snapshot()); _repository.ReplayAll();
回答7:
You will probably have to refactor your class like this, in order for it to be injected with a different dependency for ISnapshot. Your class will remain to function the same.
public class Cache { private ISnapshot _lastest_snapshot; public ISnapshot LatestSnapshot { get { return this._lastest_snapshot; } private set { this._latest_snapshot = value; } } public Cache() : this (new Snapshot()) { } public Cache(ISnapshot latestSnapshot) { this.LatestSnapshot = latestSnapshot; } public void Freeze(IUpdates Updates) { ISnapshot _next = this.LastestSnapshot.CreateNext(); _next.FreezeFrom(Updates); this.LastestSnapshot = _next; } }
回答8:
You can simply add "setSnapshot(ISnapshot)" method to the Cache with your mocked class instance.
You can also add a constructor that takes ISnapshot.
回答9:
Turn Cache into a template as shown below.
template <typename T=ISnapshot> public class Cache { private T _lastest_snapshot; public T LatestSnapshot { get { return this._lastest_snapshot; } private set { this._latest_snapshot = value; } } public Cache() { this.LatestSnapshot = new Snapshot(); } public void Freeze(IUpdates Updates) { T _next = this.LastestSnapshot.CreateNext(); _next.FreezeFrom(Updates); this.LastestSnapshot = _next; } }
In production code do:
Cache<> foo;//OR Cache<ISnapshot> bar;
In test code do:
Cache<MockSnapshot> mockFoo;