6

I need to have a logged in user for each of my unit tests, which forces me to do an async call (the login) in my test SetUp.

I can't find a way to make this work, I either get null pointer exceptions, or invalid signatures for setup.

public async void SetUp() {}

This makes all my test fail with my objects probably because I'm not logged in.

public async Task SetUp() {}

Makes all my test ignored because the setup has an invalid signature.

And I would like not to have to copy my X lines of setup in each test, since they're all exactly the same and... that's what the setup is for.

What am I missing ? this appears like a trivial problem.

Here is what I have now, for the sake of showing something

CreateTicketViewModel _viewModel;

        [SetUp()]
        public async void SetUp()    //I have tried using Task instead of void
        {

            IUserService userService = Dependency.Instance.Resolve<IUserService>();
            await userService.LoginAsync(this.UserName, this.Password);

            _viewModel = Dependency.Instance.Resolve<CreateTicketViewModel>();
        }

        [TearDown()]
        public void TearDown()
        {
            _viewModel = null;  // I have tried removing this
        }

        [Test()]
        public void Initialization()
        {
            // If I put what's in SetUp here and add "async" before void,
            // it works just fine

            Assert.IsNotNull(_viewModel);
            Assert.IsNotNull(_viewModel.Ticket);
        }
6
  • I have read many blog posts about not having async void in SUT, which I don't have, but none of these blog posts talk about the test themselves. Do I have a design problem? Commented Oct 20, 2015 at 14:16
  • Can't you make your test methods async instead of Setup method? Or you could even make SetUp method synchronous. Former is recommended though. Commented Oct 20, 2015 at 14:25
  • An async test with only asserts in it shows a warning. I'm hoping to avoid that if possible. Commented Oct 20, 2015 at 14:33
  • I don't understand how your code works. It simply uses IUserService in Setup method alone? Commented Oct 20, 2015 at 14:34
  • I'm using the UserService to log myself in (with hardcoded data in the case of the test) but that's hidden here. And then through dependency injection I'm creating my viewmodel, which requires to be logged in its constructor. Commented Oct 20, 2015 at 14:37

3 Answers 3

5

Depending on the unit test framework you use async setup may not be handled correctly by the framework.

in NUnits case I think it doesn't support yet async Setup methods.

So what you should do in your Setup is to just wait synchronously for the Login to complete:

userService.LoginAsync(this.UserName, this.Password).Wait();

EDIT: looks like it's an open issue https://github.com/nunit/nunit/issues/60

It's the case for MSTests also.

Sign up to request clarification or add additional context in comments.

Comments

2

Could you not just make your SetUp method not async at all and write this?

new Task(() => userService.LoginAsync(this.UserName, this.Password)).RunSynchronously()

2 Comments

When I do this, the content of my userService is still null when the test start, and therefore fails. Note that there are more async calls behind that LoginAsync method.
though I like the idea of running the login synchronously
2

Async Setups are not supported but async test methods are supported. You can make your test methods async instead of setup method.

[TestFixture]
public class AsyncSetupTest
{
    private Task<CreateTicketViewModel> viewModelTask;

    [SetUp()]
    public void SetUp()
    {
        viewModelTask = Task.Run(async () =>
        {
            IUserService userService = Dependency.Instance.Resolve<IUserService>();
            await userService.LoginAsync(this.UserName, this.Password);

            return Dependency.Instance.Resolve<CreateTicketViewModel>();
        });
    }

    [Test()]
    public async Task Initialization()
    {
        CreateTicketViewModel viewModel = await viewModelTask;

        Assert.IsNotNull(viewModel);
        Assert.IsNotNull(viewModel.Ticket);
    }
}

Idea is, instead of getting all the setup work done in Setup method, we create a Task representing the completion of setup and awaiting it in the Test method.

This way you're not repeating all the setup logic. But just extracting the ViewModel from Task inside all test methods.

4 Comments

this would work but would still require me to have a single repeated line in every test ; but indeed it's already better than what I did. Thank you. On the other hand, Dan Dinu's answer provides a solution that works "as is" with the ability to only have my asserts in my tests, which implies having less code and a smaller code block in my setup.
@Zil That would work. But blocking in async code is never a good Idea. You could get a deadlock. Also refer this.
I agree for production code, but I do want my tests to be waiting here while the login is in process. Do you think that is that bad in this specific scenario ? Knowing that I'm not doing any other async calls here (instead of inside the login itself)
For tests maybe blocking is fine. But if you have some custom synchronization context installed, even unit tests can deadlock. (amended the comment to add link)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.