我想对由依赖项引发的事件是否由被测类进行预订进行单元测试。要设置上下文,我具有以下接口和类。
ITestedService.cs
public interface ITestedService
{
Task Start();
Task Stop();
}
IDependency.cs
public interface IDependency
{
event EventHandler<SoAndSoEventArgs> SomethingHappened;
Task Start();
Task Stop();
}
ISecondDependency
public interface ISecondDependency
{
Task DoYourJob(SoAndSo soAndSo);
}
TestedService.cs
public class TestedService : ITestedService
{
readonly IDependency m_dependency;
readonly ISecondDependency m_secondDependency;
public TestedService(
IDependency dependency,
ISecondDependency secondDependency)
{
m_dependency = dependency;
m_secondDependency = secondDependency;
}
public async Task Start()
{
m_dependency.SomethingHappened += OnSomethingHanppened;
await m_dependency.Start();
}
private async void OnSomethingHanppened(object sender, SoAndSoEventArgs args)
{
SoAndSo soAndSo = SoAndSoMapper.MapToDTO(args);
await m_secondDependency.DoYourJob(soAndSo),
}
}
在上述情况下,我想使用Start()
对TestedService
类的xUnit
方法进行单元测试。我想知道如何:
IDependency.SomethingHappened
被触发。OnSomethingHappened
方法ISecondDependency.DoYourJob(soAndSo)
。单元测试的目的可以是:
话虽如此,您无需测试语言的内部。例如,在这种情况下,您不需要验证注册事件时是否将调用注册的方法。这是语言的工作。通过语言测试。
因此,您确认Start方法执行了预期的调用。顺便说一句,如上所述,只有在有理由这样做时才有意义,例如上面的目的2。现在您知道OnSomethingHappened将被触发。语言保证了这一点。您要测试的是OnSomethingHappened中的实际实现。为此,您需要通过使其可访问(访问修饰符private不起作用)并使其依赖关系也可模拟(SoAndSoMapper不可模拟),使该方法更具可测试性。
注:单元测试更多是使代码可测试的活动,而不是弄清楚如何编写测试的活动。如果编写测试很困难,则可能表明代码不容易测试。
public class TestedService
{
readonly IDependency m_dependency;
readonly ISomethingDoer m_somethingDoer;
public TestedService(
IDependency dependency,
ISomethingDoer somethingDoer)
{
m_dependency = dependency;
m_somethingDoer = somethingDoer;
}
public async Task Start()
{
m_dependency.SomethingHappened += m_somethingDoer.OnSomethingHanppened;
await m_dependency.Start();
}
}
interface ISomethingDoer
{
Task OnSomethingHanppened(object sender, SoAndSoEventArgs args);
}
class SomethingDoer : ISomethingDoer
{
readonly ISecondDependency m_secondDependency;
readonly ISoAndSoMapper m_soAndSoMapper;
public SomethingDoer(ISecondDependency secondDependency, ISoAndSoMapper soAndSoMapper)
{
m_secondDependency = secondDependency;
m_soAndSoMapper = soAndSoMapper;
}
public async Task OnSomethingHanppened(object sender, SoAndSoEventArgs args)
{
SoAndSo soAndSo = m_soAndSoMapper.MapToDTO(args);
await m_secondDependency.DoYourJob(soAndSo),
}
}
现在,您可以通过为SomethingDoer创建一个测试类,模拟它的依赖关系并验证例如给定的soAndSoMapper模拟返回了某个值,然后使用该值调用secondDependency来测试OnSomethingHappened的功能。尽管再次,OnSomethingHappened并没有做太多事情。因此,是否要进行测试是有争议的。
从this answer,this documentation以及@ZevSpitz的注释中,我能够为Start()编写以下测试。尽管我无法验证是否执行了相同的代码路径OnSomethingHappened
还是调用了m_secondDependencyMock.DoYourJob(soAndSo)
的其他订阅。
TestedServiceTest.cs
public class TestedServiceTest
{
readonly Mock<IDependency> m_dependencyMock;
readonly Mock<ISecondDependency> m_secondDependencyMock;
ITestedService testedService;
public TestedServiceTest()
{
m_dependencyMock = new Mock<IDependency>();
m_secondDependencyMock = new Mock<ISecondDependency>();
testedService = new TestedService(m_dependencyMock.Object, m_secondDependencyMock.Object);
}
[Fact]
public async Start_DependencyStartInvoked()
{
// Arrange
m_dependencyMock.Setup(x=> x.Start()).Verifyable();
// Act
await testedService.Start();
// Assert
//This tests if the IDependecy.Start is invoked once.
m_dependencyMock.Verify(x=>x.Start(), Times.Once);
}
[Fact]
public async Start_EventListenerAttached()
{
// Arrange
m_dependencyMock.Setup(x=> x.Start()).Verifyable();
m_dependencyMock.SetupAdd(m => m.SomethingHappened += (sender, args) => { });
// Act
await testedService.Start();
// Assert
// The below together with SetupAdd above asserts if the TestedService.Start adds a new eventlistener
// for IDependency.SomethingHappened
m_dependencyMock.VerifyAdd(
m => m.SomethingHappened += It.IsAny<EventHandler<SoAndSoEventArgs>>(),
Times.Exactly(1));
}
[Fact]
public async Start_SomthingHappenedInvoked_HandlerExecuted()
{
// Arrange
m_dependencyMock.Setup(x=> x.Start()).Verifyable();
m_secondDependencyMock.Setup(x=> x.DoYourJob(It.IsAny<SoAndSo>())).Verifyable();
// Act
await testedService.Start();
// This will fire the event SomethingHappened from m_dependencyMock.
m_dependencyMock.Raise(m => m.SomethingHappened += null, new SoAndSoEventArgs());
// Assert
// Assertion to check if the handler does its job.
m_secondDependencyMock.Verify(x=> x.DoYourJob(It.IsAny<SoAndSo>()), Times.Once);
}
}