As we have discussed in the comment section your goal is to test the returned messages
is passed to the dto's constructor without any modification.
With your current code you can't do that because you are creating the DTO explicitly inside your method. So, you can't replace it with a spy.
Just to clarify some terminologies:
- Dummy: simple code that returns bogus data
- Fake: a working alternative which can take shortcuts
- Stub: custom logic with predefined data
- Mock: custom logic with expectations (interactive stub)
- Shim: custom logic at run-time
- Spy: interceptors to record calls
To be able to capture the arguments of the MessageListDto
constructor call you need to introduce an other layer between your GetMessageList
method and the constructor. (In other words indirection)
For the sake of simplicity let me assume that IMessageRepository
's GetMessages
returns a string
array.
So, you can introduce an interface like this:
public interface IDtoProvider
{
public MessageListDto CreateMessageList(string[] messages, int total)
=> new MessageListDto(messages, total);
}
Now lets amend your MessageService
to receive a IDtoProvider
instance via its constructor
class MessageService
{
private readonly IMessageRepository _repository;
private readonly IDtoProvider _dtoProvider;
public MessageService(IMessageRepository repository, IDtoProvider dtoProvider)
=> (_repository, _dtoProvider) = (repository, dtoProvider);
public MessageListDto GetMessageList(SimpleSearchCriteria criteria)
{
var messages = _repository.GetMessages(criteria, out int total);
return _dtoProvider.CreateMessageList(messages, total);
}
...
}
With these in our hand we can write a unit test like this:
//Arrange - Repo
string[] messages = new[] { "OnlyElement" };
int total = 10;
var mockRepo = new Mock<IMessageRepository>();
mockRepo
.Setup(repo => repo.GetMessages(It.IsAny<SimpleSearchCriteria>(), out total))
.Returns(messages);
//Arrange - Provider
var dto = new MessageListDto(messages, total);
string[] messagesArgument = null;
int totalArgument = -1;
var mockProvider = new Mock<IDtoProvider>();
mockProvider
.Setup(provider => provider.CreateMessageList(It.IsAny<string[]>(), It.IsAny<int>()))
.Callback<string[], int>((messages, total) => (messagesArgument, totalArgument) = (messages, total))
.Returns(dto);
//Arrange - SUT
var searchCriteria = new SimpleSearchCriteria();
var sut = new MessageService(mockRepo.Object, mockProvider.Object);
//Act
sut.GetMessageList(searchCriteria);
//Assert - Repo
mockRepo.Verify(r => r.GetMessages(searchCriteria, out total), Times.Once);
//Assert - Provider
Assert.Equal(messages, messagesArgument);
Assert.Equal(total, totalArgument);
- I've defined how should the
IMessageRepository
mock behave
- I've defined how should the
IDtoProvider
mock behave
2.1 I've used here the Callback
method to capture the calling argument
2.2 If you perform multiple calls against your mocked method then please consider to use Capture.In
- I've instantiated a
MessageService
(which is our system under test) with the mock objects
- I've called the
GetMessageList
inside the Act phase
- I've made assertion against the repo mock call
- I've made assertion against the calling parameters of the
CreateMessageList
6.1 Here I have used xunit's assertion