Friday, December 29, 2023

Using MOQ

MOQ is a popular mocking framework for .NET used to create mock objects for testing. MOQ allows you to isolate the class under test by replacing its dependencies with controlled mock objects, making it easier to test behavior in isolation.  Here are the steps to use it.  

1.  Install MOQ.  Add MOQ using the NuGet package manager.  https://www.nuget.org/packages/Moq

2.  Create a Mock Object. Use Mock<T> where T is the type you want to mock. For example:

_webAPIClientMock = new Mock<IWebAPIClient>();

3,  Setup Method Behavior. Define how the mock object should behave. For example:

_webAPIClientMock.Setup(client => client.GetApplicationSetting(It.IsAny<GetAppSettingRequest>()).Result).Returns(appSettingResponse);

4.  Inject the mock. Inject the mock object into the class you are testing.

_apiSettingsProvider = new ApiSettingsProvider(Mock.Of<ILogger<ApiSettingsProvider>>(), _webAPIClientMock.Object);

5.  Verify Interactions. Optionally, verify that certain interactions with the mock object occurred, like _webAPIClientMock.Verify(x => x.GetApplicationSetting(), Times.Once);

6.  Run Your Test.  Execute your test method as usual.

Full Example

[TestClass]
public class ApiSettingsProviderTests
{
private  Mock<IWebAPIClient> _webAPIClientMock;
private  ApiSettingsProvider _apiSettingsProvider;

[TestInitialize]
public void Initialize()
{
_webAPIClientMock = new Mock<IWebAPIClient>();
_apiSettingsProvider = new ApiSettingsProvider(Mock.Of<ILogger<ApiSettingsProvider>>(), _webAPIClientMock.Object);
}

[TestMethod]
public void GetAppSettingValue_ShouldReturnFirstValueFromAppSettingsResponse()
{
   // Arrange
var appSettingName = "TestSetting";
var settingValues = new string[] { "Value1", "Value2" };
var appSettingResponse = new GetAppSettingResponse
{
SettingValues = new List<string>(settingValues)
};
_webAPIClientMock.Setup(client => client.GetApplicationSetting(It.IsAny<GetAppSettingRequest>()).Result).Returns(appSettingResponse);
// Act
var value = _apiSettingsProvider.GetAppSettingValue(appSettingName);
// Assert
Assert.AreEqual("Value1", value);
}
}

Resources

Creating Angular Tests and Mocking with Jasmine

When creating Angular Components, by default a spec.ts file is created.  By definition, a unit test should not call any files, databases, or services.  We use a mock, or a stand in for services.  

Mocking Services

To mock services, the jasmine.createSpyObj is used. Example:

loggingService = jasmine.createSpyObj('LoggingService', ['Info','Error']); 

Providing Mocked Services

To use the mocked service, it is provied.  Example:

    TestBed.configureTestingModule({
      providers: [
        ErrorHandlerService, 
        { provide: LoggingService, useValue: loggingService },
        { provide: HttpTestingController, useValue: httpTestingController }
      ],
    });


Full Example

We have an error handler service and we want to ensure that the message is logged by using the Error method.

import { TestBed } from '@angular/core/testing';
import { HttpErrorResponse } from '@angular/common/http';
import { ErrorHandlerService } from './error-handler.service';
import { LoggingService } from './logging.service';
import { HttpTestingController } from '@angular/common/http/testing';

describe('ErrorHandlerService', () => {
  let errorHandlerService: ErrorHandlerService;
  let loggingService: LoggingService;
  let httpTestingController: HttpTestingController;
  
  beforeEach(() => {
    loggingService = jasmine.createSpyObj('LoggingService', ['Info','Error']);
    TestBed.configureTestingModule({
      providers: [
        ErrorHandlerService, 
        { provide: LoggingService, useValue: loggingService },
        { provide: HttpTestingController, useValue: httpTestingController }
      ],
    });
    errorHandlerService = TestBed.inject(ErrorHandlerService);
    httpTestingController = TestBed.inject(HttpTestingController);
  });
  it('should be created', () => {
    expect(errorHandlerService).toBeTruthy();
  });
  it('should handle an error and log it', () => {
    spyOn(console, 'error');
    const error = new Error('Test error message');
    errorHandlerService.handleError(error);
    expect(console.error).toHaveBeenCalledWith(error);
    expect(loggingService.Error).toHaveBeenCalledWith(error.message, [error]);
  });
  
});