0

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?

Nirdha
  • 95
  • 1
  • 1
  • 9
  • 2
    `[TestCase("Hello", "5fda6c5c49f10b1464f1f2ce")]` means `Hello` will passed to first parameter of `Create_StateUnderTest_ExpectedBehavior` . But that method has `Message message` as first parameter. `Hello` can not be converted to `Message` object that's why you are getting this error. If your are not sure why you are using `TestCase` attribute you should not use it or learn why and when it should be used. – Chetan Feb 21 '21 at 15:15
  • Hi @ChetanRanpariya Thank u so much for helping. I could solve the issue in the way i have mentioned in the answer. You can correct me if i am wrong :) – Nirdha Feb 22 '21 at 06:50

3 Answers3

1

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 i.e 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.

  • Hi @Aditya I tried to do what you said. So i edited the unit test code. You can see the new one above in the question. But still I am getting the same error. Is it wrong? – Nirdha Feb 21 '21 at 17:09
  • As I understood the argument i am passing inside the testcase is a type of **System.String** but in the create method the object i am passing is a class type object. I tried to solve it but failed. – Nirdha Feb 21 '21 at 17:14
  • 1
    @kalpana as pointed out by chetan ranpariya in comment to your question, the testcase attribute will pass the argument to your test which in your case is "Hello" a string, but the test method expects a argument of type message. I am not sure if we can pass objects instead of primitive data types in test case attribute. so you can instead pass no argument to test and create your message object in test case arrange part to call api create method. – Aditya Kumbhar Feb 22 '21 at 03:55
  • 1
    for passing object to testcase you can read this thread - https://stackoverflow.com/questions/4220943/how-can-i-pass-dynamic-objects-into-an-nunit-testcase-function – Aditya Kumbhar Feb 22 '21 at 04:01
  • Hi @Aditya Thank u so much for helping. I could solve the issue in the way i have mentioned in the answer. You can correct me if i am wrong :) – Nirdha Feb 22 '21 at 06:47
1

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

var apiController = this.CreateApiController(mockMessageService);

Deep
  • 11
  • 1
  • 1
  • Hi @Deep Thank u so much for helping. I could solve the issue in the way i have mentioned in the answer. You can correct me if i am wrong :) – Nirdha Feb 22 '21 at 06:48
0

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!

Nirdha
  • 95
  • 1
  • 1
  • 9
  • 1
    the GetId method setup is not needed and can be removed I think, also the message1 is a redundant var you declared for temporary testing I suppose, so should remove that too. Also a good way is to do the setup on moq methods in the setup method having [TestInitialize] attribute, that way you don't need to repeat those setups in each test and can override those in case you need the method to return a different value then the default setup. – Aditya Kumbhar Feb 22 '21 at 07:21