2

I have following problem with testing class library with NUnit

When i call this method from console app or other project it works propertly - App will work as long as listenerThread is running.

public void StartServer(string ListenOnIp, int port, string ServerPFXCertificatePath, string CertPassword, bool VerifyClients)
        {

            serverCert = new X509Certificate2(ServerPFXCertificatePath, CertPassword, X509KeyStorageFlags.PersistKeySet);
            listener = new TcpListener(IPAddress.Parse(ListenOnIp), port);
            Thread listenerThread = new Thread(() =>
            {
                listener.Start();
                int connected = 0;
                while (listener.Server.IsBound)
                {
                    TcpClient client = listener.AcceptTcpClient();
                    SSLClient connection = new SSLClient(client, serverCert, VerifyClients, this);                    
                }
                connected++;
            });
            listenerThread.Start();            
        }

However when i try to call this method from NUnit test method, it behaves like starting fire and forget task - Test completes immediately.

I tried wrapping StartServer method like that

        [Test]
        public void Test()
        {
            Task.Run(() =>
            {
                server.StartServer(IPAddress.Any, 5000, $"{Workspace}\\{ServerWorkspace}\\Server.pfx", "123", false);
            }).Wait();
        }

and i tried marking test method like this -[Test,RequiresThread] with no result

I could change StartServer to Task and just use Wait() or Wait() for some TaskCompletionSource which completes when listenerThread ends it work, but I would like to know if there is a better way, preferably without the need to modify StartServer method.

vvvanderer
  • 21
  • 3

3 Answers3

1

Thread ctor creates so called foreground thread which will block app from exiting until it is finished. It seems that NUnit somehow ignores it. One way to workaround it to return the thread and call Join in the test:

public Thread StartServer(string ListenOnIp, int port, string ServerPFXCertificatePath, string CertPassword, bool VerifyClients)
{
    // ...
    listenerThread.Start();    
    return listenerThread;
}

And in the test:

[Test]
public void Test()
{
    var thr = server.StartServer(IPAddress.Any, 5000, $"{Workspace}\\{ServerWorkspace}\\Server.pfx", "123", false);
    thr.Join();
}

Other option would be adding some signaling mechanism from the server. Or returning Task and using TaskCompletionSource to complete it.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
1

"Normally" (whatever that means) a server you use in a test is started in either SetUp or OneTimeSetUp and terminated in the corresponding teardown.

Use the "one time" flavors if your server is designed to handle multiple calls.

Save the server reference in a member property of field and organize your tests so that the fixture contains only or mostly those cases that use the server. That makes it's NUnit's job to ensure the server starts and stops when you need it to.

BTW, [RequiresThread] indicates that your test must run on a thread of it's own, not used by any other tests. It's unusual to need this. Unfortunately, the attribute name has lead many folks to think that they do. Use it if you are doing obscure things with TLS, etc. that conflict with NUnit's thread management.

Charlie
  • 12,928
  • 1
  • 27
  • 31
  • This seems to be only good way, but unfortunately i couldn't make it work like this. However i implemented some goofy workaround that works just fine, i'll post it soon. – vvvanderer Aug 18 '23 at 09:48
0

I couldn't find better way to fix this, so i made something like this using async and TaskCompletionSource.

My server class fires an event when any transfer method on the server side completes. My idea is to block test method execution as long as server doesn't fire specified event.

This is what i did -

First i needed TaskCompletionSouce<object> TestEnder in Test Class

Every [SetUp] new instance of TaskCompletionSource is assigned to TestEnder.

Then we use this "Locker" task, which awaits TestEnder completion. It has some kind of "TimeOut" mechanism which will complete TestEnder even if something else fails, so test can like in winxp fashion - "Fail successfully" :)

async Task Locker()
        {
            Task.Run(async () =>
            {
                await Task.Delay(1000);
                if (!TestEnder.Task.IsCompleted)
                {
                    TestEnder.SetException(new Exception("Operation time out"));
                }
            });
            await this.TestEnder.Task;
        }

Having this i can now successfully test connection methods with test like that -

        [Test]
        public async Task SendStringMessageTest()
        {
            Task locker = Task.Run(() => Locker());
            
            string Received="";
            server.HandleReceivedText = (string r) =>
            {
                Received = r;
                this.TestEnder.SetResult(null); //ending locker task
            };

            client.Connect("127.0.0.1", 5000);
            client.WriteText(Encoding.UTF8.GetBytes("Test Message"));
                  
            await locker; // waiting for locker to complete
            Assert.That(Received == "Test Message;", $"Got {Received}");
        }
vvvanderer
  • 21
  • 3