import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { CannotInjectValueError, CannotInstantiateValueError, Container, ContainerInstance, ServiceNotFoundError, Token, } from '../index'; describe('Error Handling', () => { let container: ContainerInstance; beforeEach(() => { container = new ContainerInstance(`test-container-${Math.random()}`); Container.reset({ strategy: 'resetServices' }); }); afterEach(() => { container.reset({ strategy: 'resetServices' }); Container.reset({ strategy: 'resetServices' }); }); describe('ServiceNotFoundError', () => { it('should throw ServiceNotFoundError for non-existent string service', () => { expect(() => container.get('non-existent-service')).toThrow(ServiceNotFoundError); }); it('should throw ServiceNotFoundError for non-existent class service', () => { class NonExistentService {} expect(() => container.get(NonExistentService)).toThrow(ServiceNotFoundError); }); it('should throw ServiceNotFoundError for non-existent token service', () => { const token = new Token('non-existent-token'); expect(() => container.get(token)).toThrow(ServiceNotFoundError); }); it('should have correct error message for string identifier', () => { try { container.get('test-service'); } catch (error) { expect(error).toBeInstanceOf(ServiceNotFoundError); expect(error.message).toContain('Service with "test-service" identifier was not found'); expect(error.message).toContain('Register it before usage'); } }); it('should have correct error message for class identifier', () => { class TestService {} try { container.get(TestService); } catch (error) { expect(error).toBeInstanceOf(ServiceNotFoundError); expect(error.message).toContain('MaybeConstructable'); expect(error.message).toContain('Register it before usage'); } }); it('should have correct error message for token identifier', () => { const token = new Token('test-token'); try { container.get(token); } catch (error) { expect(error).toBeInstanceOf(ServiceNotFoundError); expect(error.message).toContain('Token'); expect(error.message).toContain('Register it before usage'); } }); it('should have correct error message for token without name', () => { const token = new Token(); try { container.get(token); } catch (error) { expect(error).toBeInstanceOf(ServiceNotFoundError); expect(error.message).toContain('Token'); expect(error.message).toContain('Register it before usage'); } }); it('should throw ServiceNotFoundError for getMany with non-existent service', () => { expect(() => container.getMany('non-existent-service')).toThrow(ServiceNotFoundError); }); }); describe('CannotInstantiateValueError', () => { it('should throw CannotInstantiateValueError when no type or factory provided', () => { container.set({ id: 'invalid-service' }); expect(() => container.get('invalid-service')).toThrow(CannotInstantiateValueError); }); it('should have correct error message', () => { container.set({ id: 'invalid-service' }); try { container.get('invalid-service'); } catch (error) { expect(error).toBeInstanceOf(CannotInstantiateValueError); expect(error.message).toContain('Cannot instantiate the requested value for the "invalid-service" identifier'); expect(error.message).toContain("doesn't contain a factory or a type to instantiate"); } }); it('should throw error for class identifier', () => { class TestService {} container.set({ id: TestService }); expect(() => container.get(TestService)).toThrow(CannotInstantiateValueError); }); it('should throw error for token identifier', () => { const token = new Token('test-token'); container.set({ id: token }); expect(() => container.get(token)).toThrow(CannotInstantiateValueError); }); it('should handle factory returning undefined', () => { const factory = () => undefined; container.set({ id: 'undefined-factory', factory }); // Factory returning undefined should not throw, but the value should be undefined const result = container.get('undefined-factory'); expect(result).toBeUndefined(); }); it('should handle factory returning null', () => { const factory = () => null; container.set({ id: 'null-factory', factory }); // Factory returning null should not throw, but the value should be null const result = container.get('null-factory'); expect(result).toBeNull(); }); }); describe('CannotInjectValueError', () => { it('should throw CannotInjectValueError when injecting unknown type', () => { expect(() => { // This would be caught during decoration, but we can't test it directly // since the decorator runs at class definition time class TestService { // @Inject() // This would throw CannotInjectValueError public unknown!: any; } }).not.toThrow(); // The error would be thrown during decoration, not here }); it('should have correct error message format', () => { class TestService {} const propertyName = 'testProperty'; const error = new CannotInjectValueError(TestService, propertyName); expect(error.message).toContain('Cannot inject value into "TestService.testProperty"'); expect(error.message).toContain('setup reflect-metadata properly'); expect(error.message).toContain('interfaces without service tokens'); }); }); describe('Container Disposal Errors', () => { it('should throw error when using disposed container', async () => { const testContainer = new ContainerInstance(`dispose-test-${Math.random()}`); await testContainer.dispose(); expect(() => testContainer.get('test')).toThrow('Cannot use container after it has been disposed.'); }); it('should throw error when setting service in disposed container', async () => { const testContainer = new ContainerInstance(`dispose-test-${Math.random()}`); await testContainer.dispose(); expect(() => testContainer.set({ id: 'test', value: 'test' })).toThrow( 'Cannot use container after it has been disposed.', ); }); it('should throw error when checking service in disposed container', async () => { const testContainer = new ContainerInstance(`dispose-test-${Math.random()}`); await testContainer.dispose(); expect(() => testContainer.has('test')).toThrow('Cannot use container after it has been disposed.'); }); it('should throw error when removing service in disposed container', async () => { const testContainer = new ContainerInstance(`dispose-test-${Math.random()}`); await testContainer.dispose(); expect(() => testContainer.remove('test')).toThrow('Cannot use container after it has been disposed.'); }); it('should throw error when resetting disposed container', async () => { const testContainer = new ContainerInstance(`dispose-test-${Math.random()}`); await testContainer.dispose(); expect(() => testContainer.reset()).toThrow('Cannot use container after it has been disposed.'); }); }); describe('Factory Errors', () => { it('should propagate factory errors', () => { const factory = () => { throw new Error('Factory error'); }; container.set({ id: 'error-factory', factory }); expect(() => container.get('error-factory')).toThrow('Factory error'); }); it('should handle factory class not found in container', () => { class Factory { create() { return 'created'; } } container.set({ id: 'factory-service', factory: [Factory, 'create'] }); // Should create factory instance directly since it's not in container const result = container.get('factory-service'); expect(result).toBe('created'); }); it('should handle factory class found in container', () => { class Factory { create() { return 'created-from-container'; } } container.set({ type: Factory }); container.set({ id: 'factory-service', factory: [Factory, 'create'] }); const result = container.get('factory-service'); expect(result).toBe('created-from-container'); }); }); describe('Multiple Service Errors', () => { it('should throw error when getting single service with multiple flag', () => { class TestService { public name = 'test'; } container.set({ id: 'test-services', type: TestService, multiple: true }); expect(() => container.get('test-services')).toThrow( 'Service with "test-services" identifier was not found in the container', ); }); it('should throw error when getting single service with multiple flag using class', () => { class TestService { public name = 'test'; } container.set({ type: TestService, multiple: true }); expect(() => container.get(TestService)).toThrow( 'Service with "MaybeConstructable" identifier was not found in the container', ); }); }); describe('Circular Dependency Errors', () => { it('should handle circular dependencies gracefully', () => { class ServiceA { public serviceB: ServiceB; constructor() { this.serviceB = container.get(ServiceB); } } class ServiceB { public serviceA: ServiceA; constructor() { this.serviceA = container.get(ServiceA); } } container.set({ type: ServiceA }); container.set({ type: ServiceB }); // Circular dependencies will cause stack overflow expect(() => container.get(ServiceA)).toThrow('Maximum call stack size exceeded'); }); }); describe('Invalid Reset Strategy', () => { it('should throw error for invalid reset strategy', () => { expect(() => container.reset({ strategy: 'invalid' as any })).toThrow('Received invalid reset strategy.'); }); }); describe('Service Disposal Errors', () => { it('should handle disposal errors gracefully', () => { class TestService { dispose() { throw new Error('Disposal error'); } } container.set({ type: TestService }); const instance = container.get(TestService); // Should not throw error even if disposal fails expect(() => container.remove(TestService)).not.toThrow(); }); it('should handle disposal of service without dispose method', () => { class TestService { public name = 'test'; } container.set({ type: TestService }); const instance = container.get(TestService); // Should not throw error expect(() => container.remove(TestService)).not.toThrow(); }); }); describe('Edge Cases', () => { it('should handle undefined service identifier', () => { expect(() => container.get(undefined as any)).toThrow(); }); it('should handle null service identifier', () => { expect(() => container.get(null as any)).toThrow(); }); it('should handle empty string service identifier', () => { expect(() => container.get('')).toThrow(ServiceNotFoundError); }); it('should handle service with empty value', () => { container.set({ id: 'empty-service', value: null }); const result = container.get('empty-service'); expect(result).toBeNull(); }); it('should handle service with undefined value', () => { container.set({ id: 'undefined-service', value: undefined }); const result = container.get('undefined-service'); expect(result).toBeUndefined(); }); }); });