5

I have the following function that I am trying to put under unit test

public MessageListDto GetMessageList(SimpleSearchCriteria criteria)
{
    var messages = _repository.GetMessages(criteria, out int total);
    return new MessageListDto(messages, total);
}        

and the following is my test so far, in which I am able to determine that _repository.GetMessages is call with the correct parameters.

However, how do I test that the second line is tested properly, I need to test that

  • A new object of type MessageListDto is constructed with the two parameters returned from the line above
  • the newly constructed object is returned
[Test]
public void Test1()
{
    int total = 10;
    var searchCriteria = new SimpleSearchCriteria();
    var mockRepo = new Mock<IMessageRepository>();
    var messageService = new MessageService(mockRepo.Object);
    messageService.GetMessageList(searchCriteria);
    mockRepo.Verify(r => r.GetMessages(searchCriteria, out total), Times.Once);
    mockRepo.Verity ??????????
}        
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Always Learning
  • 365
  • 6
  • 20
  • It depends. If your `MessageListDto` is pretty simple (like having only 2 properties) then use simple assertions. If your dto is more complex then please consider to use deep equality check libraries, like [Fluent Assertions](https://fluentassertions.com/) – Peter Csala Feb 14 '22 at 10:14
  • Could you please share with us the code of `MessageListDto`? – Peter Csala Feb 14 '22 at 10:35
  • MessageListDto has many properties, but I am not testing in here that the objects match exactly - I am more interested in - what gets returned from GetMessages is passed into a constructor call to MessageListDto - The newly created MessageListDto object is returned – Always Learning Feb 15 '22 at 04:36
  • Are you saying that you want to test the returned messages are passed to the dto's constructor without any modification? – Peter Csala Feb 15 '22 at 06:24
  • Correct - and that the newly created object is returned from the function without modification – Always Learning Feb 15 '22 at 19:26
  • In short: You can't. You can't since in order to be able to verify calling arguments you need to inject a spy. But you can't since you directly create a new instance. By introducing a Factory here (so adding one more abstraction layer) it can help you to be able to verify arguments. Shall I leave a post to demonstrate? – Peter Csala Feb 16 '22 at 06:54
  • 1
    I started adding a factory but thought it was overkill (and did not know exactly while). If you could post an example that would be fantastic – Always Learning Feb 16 '22 at 16:51

3 Answers3

2

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);
  1. I've defined how should the IMessageRepository mock behave
  2. 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
  3. I've instantiated a MessageService (which is our system under test) with the mock objects
  4. I've called the GetMessageList inside the Act phase
  5. I've made assertion against the repo mock call
  6. I've made assertion against the calling parameters of the CreateMessageList
    6.1 Here I have used xunit's assertion
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
0

The first step is to setup the MessageService mock, so that it returns something deterministic and then in the second step you verify that that has been used to construct your MessageListDto.

[Test]
public void Test1()
{
    // arrange
    int total = 10;
    var searchCriteria = new SimpleSearchCriteria();
    var messages = new [] {"message1", "message2"} // or similar
    var mockRepo = new Mock<IMessageRepository>();
// or similar, I am not quite certain as to the specific syntax. Especially wrt. out parameters. Check the documentation. 
    mockRepo.Setup(x => x.GetMessages(It.IsAny<SimpleSearchCriteria>(), It.IsAny<int>())).Returns(messages); 
    var messageService = new MessageService(mockRepo.Object);           

    // act
    var result = messageService.GetMessageList(searchCriteria);

    // assert
    mockRepo.Verify(r => r.GetMessages(searchCriteria, out total), Times.Once);
    // or similar; here you might want to check out FluentAssertions as @Peter Csala suggested
    Assert.Equal(result.Messages, messages); 
}    
PeterE
  • 5,715
  • 5
  • 29
  • 51
0

What's missing from previous answers is that whenever possible, you should set up the mocked method with the exact parameters, that you expect it to receive. In other words set up the method with concrete values instead of It.IsAny<>(). In such case you won't have to verify the method later at all. (Unless it's critical to test, how many times a method is called). Simply, if mocked GetMessages receives different arguments than expected, it will return null, set total to 0 and your test will fail. Having the mocked methods set up properly, you can now focus on verifying what GetMessageList returns, which is the purpose of this unit test.

[Test]
public void Test1()
{
    int total = 10;
    var messages = new[] {new Message()};
    var searchCriteria = new SimpleSearchCriteria();
    var mockRepo = new Mock<IMessageRepository>();
    mockRepo.Setup(_ => _.GetMessages(searchCriteria, out total))
        .Returns(messages);
    var messageService = new MessageService(mockRepo.Object);
    var dto = messageService.GetMessageList(searchCriteria);
    Assert.AreSame(messages, dto.Messages);
    Assert.AreEqual(total, dto.Total);
}
Rafał Rutkowski
  • 1,419
  • 10
  • 11