Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend expect method to allow additional testing #104

Open
BennieCopeland opened this issue Jan 7, 2016 · 4 comments
Open

Extend expect method to allow additional testing #104

BennieCopeland opened this issue Jan 7, 2016 · 4 comments
Labels

Comments

@BennieCopeland
Copy link
Contributor

There is not currently a way to do additional testing after an exception is thrown in an act block. The expect<T>() will detect that the exception was thrown, but does not allow for additional testing. The expect<T>(Action action) takes a delegate, but it is expecting the delegate to throw the exception, not the act block.

I would like to have a third option that detects the exception was thrown by act, but allows me to add additional testing. Since the method signature expect<T>(Action action) is already taken, perhaps a signature of expect<T>(Action<T> action). This would allow code like the following:

void SomethingSomething(ref string input)
{
    throw new ArgumentNullException();
}

void when_something_something()
{
    string input = null;

    beforeEach = () => input = "Hello World";

    act = () => { SomethingSomething(ref input); };

    it["should throw exception"] = expect<ArgumentNullException>();

    it["should have InnerException of type"] = expect<ArgumentNullException>((ex) =>
    {
        ex.InnerException.GetType().Should().Be(typeof(ArgumentException));
    });

    it["input should not be modified"] = expect<ArgumentNullException>((ex) =>
    {
        input.Should().Be("Hello World");
    });
}
@amirrajan
Copy link
Collaborator

Some of the promise libraries out there have an on_error callback.

so maybe:

on_error = (e) => { };

Keep in mind that you can inherit from nspec and do something like:

class nspec : nspec_with_error_handling
{
  public virtual void on_error(Exception e)
  {

  }

  public ActionRegister act_and_catch ....
  {
    this.act[testname] = () =>
    {
      try 
      {
      } 
      catch(Exception e)
      {
        on_error(e);
        throw;
      }
    }
  }
}

Just hesitant of adding more flow related stuff to NSpec. Don't want it to get too complex.

@BrainCrumbz
Copy link
Collaborator

Not ultra-strictly related to this request:

  • Assertion libraries like FluentAssertions allow to assert types like this: someObject.Should().BeOfType<ExpectedType>(). It allows to chain asserts with And() and Which() (although I wouldn't go too far with that). It also supports checking for thrown exceptions: ShouldThrow<TException>().WithMessage()
  • @amirrajan I guess class inheritance relationship is written the other way around

@BennieCopeland
Copy link
Contributor Author

@amirrajan
For my own testing needs, I added a method:

public Action expectCheck<T>(Action action) where T : Exception
{
    var expect = expect<T>();

    return () =>
    {
        expect();
        action();
    };
}

This works, but doesn't allow access to the actual exception (which I really didn't need for my case). I wanted to write a modified version of nspec.cs expect(string message), but the Context variable is internal. I thought it would be a nice feature to have though. My current spec where I am using it goes like this:

void describe_saving()
{
    // define variables used by all tests

    beforeEach = () => { /* initial setup for all contexts */ };

    act = () => { /* single action for all nested contexts */ };

    context["a null aggregate"] = () =>
    {
        beforeEach = () => { /* do context setup */ };

        it["will throw an ArgumentNullException"] = expect<ArgumentNullException>();
    };

    context["an aggregate with a null Id"] = () =>
    {
        beforeEach = () => { /* do context setup */ };

        it["will throw an ArgumentNullException"] = expect<ArgumentNullException>();
    };

    context["a new aggregate"] = () =>
    {
        // define variables used by this context

        beforeEach = () => { /* do context setup */ };

        it["will add the events to the stream"] = () => {  /* do checks */ };

        it["will mark the changes as committed"] = () =>  {  /* do checks */ };
    };

    context["an existing aggregate with new changes"] = () =>
    {
        // define variables used by this context

        beforeEach = () => { /* do context setup */ };

        it["will add the events to the stream"] = () => {  /* do checks */ };

        it["will mark the changes as committed"] = () => {  /* do checks */ };

        context["and a new version has been saved in the background"] = () =>
        {
            // define variables used by this context

            beforeEach = () => { /* do context setup */ };

            it["will throw a ConcurrencyException"] = expect<ConcurrencyException>();

            it["will not mark the changes as committed"] = expectCheck<ConcurrencyException>(() =>
            {
                // check that something didn't happen
            });
        };
    };
}

Down at the very bottom, the it["will not mark the changes as committed"] is where I need to test something outside just the exception being thrown. The exception it's catching on is thrown from its parent context of describe_saving.

On a side note, is there an attribute or something I can use where I identify the method and set its name to a single word? Like [Context( "Saving" )] or something? I know NSpec prides itself on not requiring attributes, but sometimes I just want a one word method spec. Or maybe instead of an attribute, prepend the method name with defined identifiers that get stripped off the output like describe, context, it, etc.

@BrainCrumbz
I can and do use FluentAssertions and use ShouldThrow<T>(), but in this instance, I am sharing a single act among several contexts. I thought about wrapping the call in act with a try/catch block, but then I need to define an Exception variable and it just doesn't seem as elegant a solution as what the expect<T> provides.

@amirrajan
Copy link
Collaborator

I'm fine with adding additional stuff if needed, just wanted show the inheritance technique and see if that would suffice. As long as the wiki entry is updated, I'm good with better exception handling changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants