Search code examples
c#unit-testingbddxunitfakeiteasy

How to test for exceptions thrown using xUnit, SubSpec and FakeItEasy


I’m using xUnit, SubSpec and FakeItEasy for my unit tests. I’ve so far created some positive unit tests like the following:

"Given a Options presenter"
    .Context(() =>
        presenter = new OptionsPresenter(view,
                                         A<IOptionsModel>.Ignored,
                                         service));

"with the Initialize method called to retrieve the option values"
    .Do(() => 
        presenter.Initialize());

"expect the view not to be null"
    .Observation(() =>
        Assert.NotNull(view));

"expect the view AutoSave property to be true"
    .Observation(() => Assert.True(view.AutoSave));

But now I want to write some negative unit tests and check that certain methods don't get called, and an exception is thrown

e.g.

"Given a Options presenter"
    .Context(() =>
        presenter = new OptionsPresenter(view,
                                         A<IOptionsModel>.Ignored,
                                         service));

"with the Save method called to save the option values"
    .Do(() => 
        presenter.Save());

"expect an ValidationException to be thrown"
    .Observation(() =>
        // TODO 
     );

"expect an service.SaveOptions method not to be called"
    .Observation(() =>
        // TODO 
     );

I can see FakeItEasy has a MustNotHaveHappened extension method, and xUnit has an Assert.Throws method.

But how do I put it all together?

The exception I want to test for should occur when the Save method is called. So I’m guessing I should wrap an Assert.Throws method around the presenter.Save() method call, but I thought the presenter.Save method should be called in the .Do(() => ...

Can you please advise if my unit test should look like below or something else?

"Given a Options presenter"
    .Context(() =>    
        presenter = new OptionsPresenter(view,
                                         model,
                                         service));

"expect the Presenter.Save call to throw an Exception"
    .Observation(() =>
        Assert.Throws<FluentValidation.ValidationException>(() => presenter.Save()));

"expect the Service.SaveOptions method not to be called"
    .Observation(() =>
        A.CallTo(() => service.SaveOptions(A<IOptionsModel>.Ignored)).MustNotHaveHappened());

Many thanks


Solution

  • I would do it like this:

    "Given a Options presenter"
        .Context(() =>
            presenter = new OptionsPresenter(view,
                                             (IOptionsModel)null,
                                             service));
    
    "with the Save method called to save the option values"
        .Do(() => 
            exception = Record.Exception(() => presenter.Save()));
    
    "expect an ValidationException to be thrown"
        .Observation(() =>
            Assert.IsType<ValidationException>(exception)
         );
    
    "expect an service.SaveOptions method not to be called"
        .Observation(() =>
            A.CallTo(() => service.SaveOptions(A<IOptionsModel>.Ignored)).MustNotHaveHappened()
         );
    

    Or better still, switching SubSpec for xBehave.net and introducing FluentAssertions:-

    "Given an options presenter"
        .x(() => presenter = new OptionsPresenter(view, (IOptionsModel)null, service));
    
    "When saving the options presenter"
        .x(() => exception = Record.Exception(() => presenter.Save()));
    
    "Then a validation exception is thrown"
        .x(() => exception.Should().BeOfType<ValiationException>());
    
    "And the options model must not be saved"
        .x(() => A.CallTo(() =>
            service.SaveOptions(A<IOptionsModel>.Ignored)).MustNotHaveHappened());