Xamarin.Essentials.NotImplementedInReferenceAssemblyException thrown when Unit Testing Xamarin.Forms app

2.3k views Asked by At

I am running Unit Tests for my Xamarin.Forms application, and the Unit Tests throw Xamarin.Essentials.NotImplementedInReferenceAssemblyException:

enter image description here

I have created a Unit Test project for the app (using NUnit 3.12.0) and have written the below code to test the functionality.

[TestFixture()]
public class Test
{
    [Test()]
    public void TestCase()
    {
        AutoResetEvent autoEvent = new AutoResetEvent(true);

        SomeClass someClass = new SomeClass();
        someClass.SomeFunction((response) =>
        {
            Assert.AreEqual(response, "Hello")
            autoEvent.Set();
        });

        autoEvent.WaitOne(); //** Xamarin.Essentials.NotImplementedInReferenceAssemblyException thrown here**
    }
}

Below is the code under test from the Xamarin.Forms app:

public class SomeClass
{
    
    public void SomeFunction(Action<string> callback)
    {
        // asynchronous code...
        callback("Hello");
    }
}

The above functionality works fine in the Xamarin.Forms app.

Note: I read that await/async can be used, however, I will have to make changes in the entire project. This is not a feasible solution for now.


Edit 1: I have created a sample Xamarin.Forms project with Unit Tests in it. The project is available here

4

There are 4 answers

2
Nkosi On BEST ANSWER

While you have stated that the subject function cannot be made async at this time, the test however can be made async.

TaskCompletionSource can be used to wait for the callback to be invoked.

[TestFixture()]
public class Test {
    [Test()]
    public async Task TestCase() {
        //Arrange
        TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
        Action<string> callback = (arg) => {
            tcs.TrySetResult(arg);
        };
        SomeClass someClass = new SomeClass();

        //Act
        someClass.SomeFunction(callback);            
        string response = await tcs.Task;

        //Assert
        Assert.AreEqual(response, "Hello")
    }
}
0
Jai Gupta On

I checked your code and it is not throwing any Xamarin.Essentials exception. To run the unit test, I had to add the "NUnit3TestAdapter" nuget package.

You will have to wait for callback otherwise test will crash. You can use TaskCompletionSource suggested by @Nkosi.

[Test()]
        public async Task TestCase()
        {

            XamarinMock.Init();

            WebApi webApi = new WebApi();

            TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>();
            Action<HttpResponseMessage> callback = (arg) => {
                tcs.TrySetResult(arg);
            };

            webApi.Execute(callback);

            var response = await tcs.Task;
            Console.WriteLine(response);
            Assert.AreEqual(response.StatusCode, HttpStatusCode.OK);
        }
4
Brandon Minnick On

You need to add the Xamarin.Essentials NuGet Package to your Unit Test Project.

I highly recommend using Dependency Injection + Xamarin.Essentials.Interfaces, because certain Xamarin.Essentials APIs aren't implemented on .NET Core and you'll need to create your own implementation.

For example, here are some of the Xamarin.Essentials APIs I implemented in .NET Core for Unit Testing my GitTrends app:

4
SomeStudent On

The issue can be related to what exactly the call back is trying to do. For example the Connectivity function of Xamarin.Essentials won't work (and will throw that type of exception) if you are calling it from your unit test as it is not running on a mobile platform and thus doesn't have that feature implemented.

A solution to this, at least one I've done for that type of functionality, is to create an interface. So, to continue with the example above regarding the Connectivity feature we can do this:

  1. Make an interface

    public interface IConnectivityService {
    
       bool CanConnect()
    }
    
  2. Define both an actual implementation of it that will be used during the app running, and one for testing purposes

    public ConnectivityService : IConnectivityService {
    
       //implement the method
    
    }
    
    public ConnectivtyServiceMock : IConnectivityService {
    
       //implement the method, but this is a mock, so it can be very skinny
    
    }
    
  3. Update your View/VM to accept this interface, and inside of the test case instantiate the mock version of it and pass that in.

In such scenarios where we have to reference device specific implementations from a unit test, we have to abstract it away in an interface and use that implementation instead