Fix unit Test

This commit is contained in:
Lorenz Hilpert
2025-10-02 20:25:50 +02:00
parent 23151474e4
commit eea5c23ce9
2 changed files with 173 additions and 76 deletions

View File

@@ -1,27 +1,21 @@
import { TestBed } from '@angular/core/testing';
import { of, throwError } from 'rxjs';
import { of, throwError, NEVER } from 'rxjs';
import { RemissionStockService } from './remission-stock.service';
import { StockService } from '@generated/swagger/inventory-api';
import { ResponseArgsError } from '@isa/common/data-access';
import { Stock, StockInfo } from '../models';
jest.mock('@generated/swagger/inventory-api', () => ({
StockService: jest.fn(),
}));
/**
* Unit tests for RemissionStockService.
* Tests the service's ability to fetch and cache stock information.
* These tests are isolated and do not depend on each other.
*
* @group unit
* @group services
*/
describe('RemissionStockService', () => {
let service: RemissionStockService;
let mockStockService: {
StockCurrentStock: jest.Mock;
StockInStock: jest.Mock;
};
let mockStockService: jest.Mocked<StockService>;
const mockStock: Stock = {
id: 123,
@@ -45,13 +39,11 @@ describe('RemissionStockService', () => {
];
beforeEach(() => {
// Clear all mocks before each test
jest.clearAllMocks();
// Create a fresh mock for each test to ensure isolation
mockStockService = {
StockCurrentStock: jest.fn(),
StockInStock: jest.fn(),
};
} as unknown as jest.Mocked<StockService>;
TestBed.configureTestingModule({
providers: [
@@ -60,9 +52,16 @@ describe('RemissionStockService', () => {
],
});
// Create a fresh service instance for each test
service = TestBed.inject(RemissionStockService);
});
afterEach(() => {
// Clear all mocks after each test to prevent interference
jest.clearAllMocks();
jest.restoreAllMocks();
});
/**
* Tests for fetchAssignedStock method.
* Verifies caching behavior and API interaction.
@@ -70,7 +69,7 @@ describe('RemissionStockService', () => {
describe('fetchAssignedStock', () => {
it('should fetch stock from API', async () => {
mockStockService.StockCurrentStock.mockReturnValue(
of({ result: mockStock, error: null }),
of({ result: mockStock, error: false }),
);
const result = await service.fetchAssignedStock();
@@ -80,7 +79,7 @@ describe('RemissionStockService', () => {
});
it('should throw ResponseArgsError when API returns error', async () => {
const errorResponse = { error: 'API Error', result: null };
const errorResponse = { error: true, result: undefined };
mockStockService.StockCurrentStock.mockReturnValue(of(errorResponse));
await expect(service.fetchAssignedStock()).rejects.toThrow(
@@ -90,7 +89,7 @@ describe('RemissionStockService', () => {
it('should throw ResponseArgsError when API returns no result', async () => {
mockStockService.StockCurrentStock.mockReturnValue(
of({ error: null, result: null }),
of({ error: false, result: undefined }),
);
await expect(service.fetchAssignedStock()).rejects.toThrow(
@@ -98,20 +97,24 @@ describe('RemissionStockService', () => {
);
});
it('should handle abort signal', async () => {
it('should handle abort signal by configuring the observable pipeline', async () => {
// Arrange
const abortController = new AbortController();
const mockObservable = {
pipe: jest.fn().mockReturnThis(),
};
mockStockService.StockCurrentStock.mockReturnValue(mockObservable);
const pipeSpy = jest.fn().mockReturnValue(
of({ result: mockStock, error: false }),
);
mockStockService.StockCurrentStock.mockReturnValue({
pipe: pipeSpy,
} as any);
try {
await service.fetchAssignedStock(abortController.signal);
} catch {
// Expected to fail since we're not properly mocking the observable
}
// Act
const result = await service.fetchAssignedStock(abortController.signal);
expect(mockObservable.pipe).toHaveBeenCalled();
// Assert
expect(result).toEqual(mockStock);
expect(pipeSpy).toHaveBeenCalled();
expect(mockStockService.StockCurrentStock).toHaveBeenCalled();
});
it('should handle API observable errors', async () => {
@@ -132,19 +135,23 @@ describe('RemissionStockService', () => {
describe('fetchStock', () => {
/**
* Valid test parameters for stock fetching.
* Note: The schema expects 'stockId', not 'assignedStockId'
*/
const validParams = {
assignedStockId: 123,
stockId: 123,
itemIds: [100, 101],
};
it('should fetch stock info successfully', async () => {
it('should fetch stock info successfully with stockId provided', async () => {
// Arrange
mockStockService.StockInStock.mockReturnValue(
of({ result: mockStockInfo, error: null }),
of({ result: mockStockInfo, error: false }),
);
// Act
const result = await service.fetchStock(validParams);
// Assert
expect(result).toEqual(mockStockInfo);
expect(mockStockService.StockInStock).toHaveBeenCalledWith({
stockId: 123,
@@ -152,69 +159,118 @@ describe('RemissionStockService', () => {
});
});
it('should throw ResponseArgsError when API returns error', async () => {
const errorResponse = { error: 'API Error', result: null };
mockStockService.StockInStock.mockReturnValue(of(errorResponse));
await expect(service.fetchStock(validParams)).rejects.toThrow(
ResponseArgsError,
);
});
it('should throw ResponseArgsError when API returns no result', async () => {
mockStockService.StockInStock.mockReturnValue(
of({ error: null, result: null }),
);
await expect(service.fetchStock(validParams)).rejects.toThrow(
ResponseArgsError,
);
});
it('should handle abort signal', async () => {
const abortController = new AbortController();
const mockObservable = {
pipe: jest.fn().mockReturnThis(),
};
mockStockService.StockInStock.mockReturnValue(mockObservable);
try {
await service.fetchStock(validParams, abortController.signal);
} catch {
// Expected to fail since we're not properly mocking the observable
}
expect(mockObservable.pipe).toHaveBeenCalled();
});
it('should validate params with schema', async () => {
// This will be validated by the schema
const invalidParams = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
assignedStockId: 'invalid' as any, // Should be number
it('should fetch assigned stock when stockId not provided', async () => {
// Arrange
const paramsWithoutStockId = {
itemIds: [100, 101],
};
mockStockService.StockCurrentStock.mockReturnValue(
of({ result: mockStock, error: false }),
);
mockStockService.StockInStock.mockReturnValue(
of({ result: mockStockInfo, error: null }),
of({ result: mockStockInfo, error: false }),
);
// The schema parsing should throw an error for invalid params
// Act
const result = await service.fetchStock(paramsWithoutStockId);
// Assert
expect(result).toEqual(mockStockInfo);
expect(mockStockService.StockCurrentStock).toHaveBeenCalled();
expect(mockStockService.StockInStock).toHaveBeenCalledWith({
stockId: 123, // Should use the fetched assigned stock ID
articleIds: [100, 101],
});
});
it('should throw ResponseArgsError when API returns error', async () => {
// Arrange
const errorResponse = { error: true, result: undefined };
mockStockService.StockInStock.mockReturnValue(of(errorResponse));
// Act & Assert
await expect(service.fetchStock(validParams)).rejects.toThrow(
ResponseArgsError,
);
expect(mockStockService.StockInStock).toHaveBeenCalled();
});
it('should throw ResponseArgsError when API returns no result', async () => {
// Arrange
mockStockService.StockInStock.mockReturnValue(
of({ error: false, result: undefined }),
);
// Act & Assert
await expect(service.fetchStock(validParams)).rejects.toThrow(
ResponseArgsError,
);
expect(mockStockService.StockInStock).toHaveBeenCalled();
});
it('should handle abort signal by configuring the observable pipeline', async () => {
// Arrange
const abortController = new AbortController();
const pipeSpy = jest.fn().mockReturnValue(
of({ result: mockStockInfo, error: false }),
);
mockStockService.StockInStock.mockReturnValue({
pipe: pipeSpy,
} as any);
// Act
const result = await service.fetchStock(validParams, abortController.signal);
// Assert
expect(result).toEqual(mockStockInfo);
expect(pipeSpy).toHaveBeenCalled();
expect(mockStockService.StockInStock).toHaveBeenCalled();
});
it('should validate params with schema and reject invalid stockId type', async () => {
// Arrange - This will be validated by the schema
const invalidParams = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
stockId: 'invalid' as any, // Should be number
itemIds: [100, 101],
};
// Act & Assert - The schema parsing should throw an error for invalid params
await expect(service.fetchStock(invalidParams)).rejects.toThrow();
// Should not reach the API call
expect(mockStockService.StockInStock).not.toHaveBeenCalled();
});
it('should validate params with schema and reject invalid itemIds type', async () => {
// Arrange
const invalidParams = {
stockId: 123,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
itemIds: 'not-an-array' as any, // Should be array
};
// Act & Assert
await expect(service.fetchStock(invalidParams)).rejects.toThrow();
expect(mockStockService.StockInStock).not.toHaveBeenCalled();
});
it('should handle empty itemIds array', async () => {
// Arrange
const paramsWithEmptyItems = {
assignedStockId: 123,
stockId: 123,
itemIds: [],
};
mockStockService.StockInStock.mockReturnValue(
of({ result: [], error: null }),
of({ result: [], error: false }),
);
// Act
const result = await service.fetchStock(paramsWithEmptyItems);
// Assert
expect(result).toEqual([]);
expect(mockStockService.StockInStock).toHaveBeenCalledWith({
stockId: 123,
@@ -223,23 +279,30 @@ describe('RemissionStockService', () => {
});
it('should handle API observable errors', async () => {
// Arrange
mockStockService.StockInStock.mockReturnValue(
throwError(() => new Error('Network error')),
);
// Act & Assert
await expect(service.fetchStock(validParams)).rejects.toThrow(
'Network error',
);
expect(mockStockService.StockInStock).toHaveBeenCalled();
});
it('should return empty array when API returns empty result', async () => {
// Arrange
mockStockService.StockInStock.mockReturnValue(
of({ result: [], error: null }),
of({ result: [], error: false }),
);
// Act
const result = await service.fetchStock(validParams);
// Assert
expect(result).toEqual([]);
expect(mockStockService.StockInStock).toHaveBeenCalled();
});
});
});