简体   繁体   中英

Unit Test api CRUD operation with Moq

I am completely new to unit testing and I tried to write unit testing for crud operation. I could write unit tests for get by id and get all . But the problem arises when writing the unit test for create and update .

This is my controller code for the create method

public async Task<IActionResult> Create([FromBody] Message message)
    {
        var duplicatemessage = await _messageService.DuplicateMessage(message.Text);
        if (duplicatemessage == null)
        {
            _messageService.Create(message);
            return CreatedAtRoute("Api", new { id = message.Id.ToString() }, message);
        }
        else
        {
            return BadRequest(new { message = "Text Already Exist" });
        }
    }

This is the unit test I wrote for the create method

[TestCase("Hello")]
    public async Task Create_StateUnderTest_ExpectedBehavior(Message message)
    {
        // Arrange
        // mockMessageService.Setup(t => t.GetId(id)).ReturnsAsync(new Message());
        mockMessageService.Setup(t => t.DuplicateMessage(message.Text)).ReturnsAsync(new Message());
        mockMessageService.Setup(t => t.Create(message)).Returns(new Message());
        var apiController = this.CreateApiController();
        //var message1 = new Message() { Text = "Hello" };

        // Act
        var result = await apiController.Create(message);

        // Assert
        Assert.IsInstanceOf<CreatedAtRouteResult>(result);
        this.mockRepository.VerifyAll();
    }

When I run the test I am getting this error

Message: System.ArgumentException: Object of type 'System.String' cannot be converted to type 'HelloApi.Models.Message'.

Stack Trace: RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast) RuntimeType.CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr) MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig) RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) MethodBase.Invoke(Object obj, Object[] parameters) Reflect.InvokeMethod(MethodInfo method, Object fixture, Object[] args)

I tried to debug but debug point did not hit. and I tried, Test api CRUD operation with Moq and How can I convert an Class Object into String? this but I don't think it is related to my problem. can anyone help me, please?

You are trying to test the method 'create' on your Api controller. so lets take a look at the dependencies of your Api controller. it depends on _messageService and specifically these two methods-

  1. DuplicateMessage
  2. Create

Now in your test case I see you have setup the moq of messageservice 'GetId' method, but no setup for the two above dependent methods. so first you need to setup the 'DuplicateMessage' method to return the type you expect it to return. you can have 2 tests one when it return null to test if block and another to when it returns a expected value ie not null to test else block.

for if block test you would need to do a mock setup for 'create' method on messageService. and then make it return the expected type to validate it.

you need to inject your mock services in constructor initialization. something like this.

var apiController = this.CreateApiController(mockMessageService);

I could solve the issue in this way.

This test is for creating a new message

Here I take two parameters id and Text and I equal it to my model class variables Id and Text by using,

var message = new Message() { Text = Text, Id = id };

[TestCase("Hello", "5fda6c5c49f10b1464f1f2ce")]
    public async Task Create_StateUnderTest_ExpectedBehavior(string Text, string id)
    {

        // Arrange
        var message = new Message() { Text = Text, Id = id };
        mockMessageService.Setup(t => t.GetId(id)).ReturnsAsync(new Message());
        mockMessageService.Setup(t => t.DuplicateMessage(message.Text)).ReturnsAsync(new Message());
        mockMessageService.Setup(t => t.Create(message)).Returns(new Message());
        var apiController = this.CreateApiController();
        var message1 = new Message() { Text = "Hellooo", Id = "5fda6c5c49f10b1464f1f2ca" };

        // Act
        var result = await apiController.Create(message1);

        // Assert
        Assert.IsInstanceOf<CreatedAtRouteResult>(result);
        this.mockRepository.Verify();
    }

So here the values passing from testcase and messsage1 are not equal, it is not a duplicate message so it should create one. Hence the test case is success.

This test is for duplicate check

[TestCase("Hello", "5fda6c5c49f10b1464f1f2ce")]
    public async Task Create_StateUnderTest_UnExpectedBehavior(string Text, string id)
    {

        // Arrange
        var message = new Message() { Text = Text, Id = id };
        mockMessageService.Setup(t => t.GetId(id)).ReturnsAsync(new Message());
        mockMessageService.Setup(t => t.Create(message)).Returns(new Message());
        mockMessageService.Setup(t => t.DuplicateMessage(message.Text)).ReturnsAsync(new Message());
        var apiController = this.CreateApiController();
        var message1 = new Message() { Text = "Hello", Id = "5fda6c5c49f10b1464f1f2ce" };

        // Act
        var result = await apiController.Create(message1);

        // Assert
        Assert.IsInstanceOf<BadRequestObjectResult>(result);
        this.mockRepository.Verify();
    }

The problem before was the way I passed the object. And now this is working fine for me. You can correct me if I am wrong:) Thank you all for helping!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM