Reputation: 9242
I am using moq4 for mocking things in my UnitTests. I have a class say TestClass in which a method called TestMethod is their that i wanted to test. So my problem is my TestMethod requires a check on testlist that is in my TestClass. Like this:-
public Class TestClass
{
public readonly ISomeService _someService;
public bool TestProperty { get; private set; }
public List<string> testlist { get; private set; }
Public TestClass(ISomeService someService)
{
_someService = someService;
}
public async Task<bool> TestMethod(string sd)
{
if(TestProperty) // here how can i control this property in UnitTest.
return false;
testlist.contains(sd); // check in this list but list will be null.
}
public async Task<List<string>> SetTestListAndProperty()
{
testlist.Add("2");
testlist.Add("3");
TestProperty = false;
}
}
Now in testmethodtests i have mocked the ISomeService and passed to TestClass constructor.
var someservicemock = new Mock<ISomeService>();
var testclassobj = new TestClass(someservicemock.Object);
Now i called My TestMethod
result = await testclassobj.TestMethod("id"); // it is throwing the exception that testlist is null.
// I am also wanted to test the TestMethod when TestProperty Is false. So how can i set this property it does not have public setter. And it is being set by other methd(SetTestListAndProperty).
So my question is can i mock the TestClass testlist without mocking the TestClass ? If this way is wrong please let me know, or you know any workaround then also. cheers
Upvotes: 2
Views: 2666
Reputation: 16209
Two things:
I wouldn't recommend to mock everything you can. In case of collections it is totally okay, to use real (not mocked) objects. Where is the benefit when you mock the list? You can very easily test just everything by examining the testlist
property.
(This is a side note, but also addresses your first comment.) Try to make your types immutable. You can find numerous articles about this topic. Just search for it. The benefit is that you don't have to deal with NullReferenceException
s and a bunch of other scenarios. For your example this means, that you should initialize testlist
in the constructor.
Upvotes: 1
Reputation: 68660
I think your question boils down to: what to do when the method under test depends on internal state that is set by some other method?
Here's an example:
class MyCollection<T> : IEnumerable<T>
{
private List<T> _list = new List<T>();
private Receiver _receiver;
public MyCollection()
{
_receiver = receiver;
}
public void Add(T item)
{
_list.Add(item);
}
public void RemoveAndNotify(T item)
{
_list.Remove(item);
_receiver.Notify(item);
}
public IEnumerator<T> GetEnumerator()
{
return _list.GetEnumerator();
}
}
(by the way, calling your types TestClass
, TestProperty
and testlist
doesn't really help - the reader has no idea how these things relate to each other. It's amost as confusing as the darned Foo, Bar and Baz. Use cats, dogs, people, etc, concrete and meaningful names instead.)
RemoveAndNotify
method. But you can't remove an item without adding one first!
Does this mean I have to mock the internal _list
to insert dummy data? NO!I think the crux of your issue is: a unit test doesn't necessarily test one method! A unit test tests the smallest possible unit of behaviour.
That unit of behaviour may involve calling one or more methods. So here's how I would unit test the behaviour related to removing items. There's two behaviours we want to test.
[Fact]
public void RemoveAndNotify_RemovesItem()
{
//Arrange
var mockReceiver = ...
var collection = new MyCollection<int>(mockReceiver.Object);
collection.Add(5);
//Act
collection.Remove(5);
//Assert
//internally calls GetEnumerator to verify that the collection no longer contains "5"
Assert.AreEqual(Enumerable.Empty<int>(), collection);
}
[Fact]
public void RemoveAndNotify_NotifiesReceiver()
{
//Arrange
var mockReceiver = ...
var collection = new MyCollection<int>(mockReceiver.Object);
collection.Add(5);
//Act
collection.Remove(5);
//Assert
mockReceiver.Verify(rec => rec.Notify(5), Times.Once());
}
As you can see, the first unit test called three methods on the class (Add
, Remove
and GetEnumerator
) in order to test one unit of behaviour.
Bottom line: forget about "methods under test", and think about "behaviour under test". You're testing behaviours, not methods.
Upvotes: 1
Reputation: 2565
I would create another constructor that takes the list as a parameter so you can create and populate a list with test data, in your test, and then give it to the class under test.
public TestClass(ISomeService service, IList<string> list) {
_someService = service;
testList = list;
}
Using this constructor, testList wont be null unless you pass null in.
I know some don't like to add methods only used for tests. If you don't want to expose another constructor that will only be used for tests you can mark the new constructor internal
and expose internals only to your test assembly.
internal TestClass(ISomeService service, IList<string> list) {
...
}
In the project's AssemblyInfo.cs
:
[assembly: InternalsVisibleTo("your test assembly");
Edit: For the properties. If you have properties that don't expose setters, you can mark the properties virtual
and use Moq's SetupGet
method to setup the properties return value.
public class ClassUnderTest
{
public virtual string TestProperty { get; private set; }
}
In your test:
public void SomeTest() {
var mock = new Mock<ClassUnderTest>();
mock.SetupGet(m => m.TestProperty).Returns("hi");
var result = mock.Object.TestProperty;
Assert.That(result, Is.EqualTo("hi"); // should pass
}
Upvotes: 0