Replace algorithm testing with insertion effects testing
As expected, rule 8 that we do not test the algorithm of methods in the article “ Rules for implementing TDD in an old project ” caused the most questions “how” and “why”. At the time of writing this article, it seemed obvious to me, so I did not dwell on this point in more detail. But since There were many questions, I want to describe my vision. Therefore, under the cat there will be a small code example and two examples of how it could be tested.
In order not to force you to run to the previous article, I will give the rule “Do not test the algorithm of methods” once again, as it sounded there:
There is the following handler code:
It is necessary to test the operation of the Handle () method. The question is to make sure that the DbCommands and MessagingLogger methods have been called.
He would pass the corresponding interfaces to the constructor of the moki class, and then check whether the corresponding methods were called or not: SaveEvt (), Received (), or InvalidEvent (). The code would look something like this:
He would create fake objects and check if the event as a whole was completed, and not the method call. In this case, the code would be something like this:
And fake-objects methods would look like this:
In this case, IsEventSaved would be declared only in a fake object.
The first approach is simple and quick, but if you need to change methods, call one instead of the other in the same situation, then the tests would need to be corrected.
The second approach leads to the creation of additional entities, and the gain is obtained only in the situation with the replacement of methods. In this case, you may not even have to change anything in Fakes or tests. Another plus, though more idealistic, is that the non-mokoist makes the test so that he does not know about the internal structure of the tested method. Therefore, I personally, if time permits, do tests for fakes.
In order not to force you to run to the previous article, I will give the rule “Do not test the algorithm of methods” once again, as it sounded there:
Here, the name of the rule was unsuccessfully selected, but has not yet come up with the best. Among the “Mochists” (those who get wet in the tests) there are those who check the number of calls to certain methods, verify the call itself, etc. In other words, it checks the internal workings of the methods. This is just as bad as private testing. The difference is only in the level of application of such a check. This approach again gives many fragile tests, which is why TDD is not perceived by some as normal.
There is the following handler code:
public class SomeEventHandler
{
public SomeEventHandler(IDatabaseCommands dbCommands,
IEventValidator validator,
IMessagingLogger messagingLogger)
{
// skipped
}
public HandlerResult Handle(EventPayload payload)
{
if (Validator.IsOurEvent(payload))
if (Validator.IsValid(payload))
{
var evt = Mapper.Map(payload);
try
{
using (var tran = new TransactionScope())
{
DbCommands.SaveEvt(evt);
MessagingLogger.Received(payload);
tran.Complete();
}
}
catch (Exception ex)
{
return MessageHandlerResult.Fatal;
}
}
else
{
var error = Validator.GetErrors();
MessagingLogger.InvalidEvent(payload, error);
return MessageHandlerResult.Fatal;
}
return MessageHandlerResult.Success;
}
}
It is necessary to test the operation of the Handle () method. The question is to make sure that the DbCommands and MessagingLogger methods have been called.
The Mochist Approach
He would pass the corresponding interfaces to the constructor of the moki class, and then check whether the corresponding methods were called or not: SaveEvt (), Received (), or InvalidEvent (). The code would look something like this:
public void Should_save_valid_data_and_log_to_messaging_events()
{
var builder = new EventPayload {
// skipped
};
var validator = Mock.Of();
var dbCommands = new Mock();
var messagingLogger = new Mock();
var handler = new SomeEventHandler(dbCommands, validator, messagingLogger);
var result = handler.Handle(payload);
// assertions
Assert.Equal(MessageHandlerResult.Success, result);
dbCommands.Verify(m => m.SaveEvt(It.IsAny(), Times.Once())
messagingLogger.Verify(m => m.Received(It.IsAny(), Times.Once())
}
The Nemokist Approach
He would create fake objects and check if the event as a whole was completed, and not the method call. In this case, the code would be something like this:
public void Should_save_valid_data_and_log_to_messaging_events()
{
var builder = new EventPayload {
// skipped
};
var validator = Mock.Of();
var dbCommands = new FakeDatabaseCommands();
var messagingLogger = new FakeMessagingLogger();
var handler = new SomeEventHandler(dbCommands, validator, messagingLogger);
var result = handler.Handle(payload);
// assertions
Assert.Equal(MessageHandlerResult.Success, result);
Assert.True(dbCommands.IsEventSaved);
Assert.True(messagingLogger.IsEventRegistered);
}
And fake-objects methods would look like this:
public void SaveEvt(Event evt)
{
IsEventSaved = true;
}
In this case, IsEventSaved would be declared only in a fake object.
Pros and cons
The first approach is simple and quick, but if you need to change methods, call one instead of the other in the same situation, then the tests would need to be corrected.
The second approach leads to the creation of additional entities, and the gain is obtained only in the situation with the replacement of methods. In this case, you may not even have to change anything in Fakes or tests. Another plus, though more idealistic, is that the non-mokoist makes the test so that he does not know about the internal structure of the tested method. Therefore, I personally, if time permits, do tests for fakes.