Una introducción práctica a la inyección de dependencia

 

 

 

  • Diseño de arquitectura de componentes de interfaz de usuario y tokens, con Nathan Curtis
  • Listas de verificación de diseño de interfaz inteligente

  • Índice
    1. Una explicación sencilla
    2. Un ejemplo del mundo real

    Este artículo es la primera parte de una próxima serie que proporciona una introducción práctica a la inyección de dependencia de una manera que le permite comprender inmediatamente sus numerosos beneficios sin verse obstaculizado por la teoría.

     

    El concepto de inyección de dependencia es, en esencia, una noción fundamentalmente simple. Sin embargo, comúnmente se presenta junto con los conceptos más teóricos de inversión de control, inversión de dependencia, principios SOLID, etc. Para que le resulte lo más fácil posible comenzar a utilizar la inyección de dependencia y comenzar a cosechar sus beneficios, este artículo se centrará en gran medida en el lado práctico de la historia y mostrará ejemplos que muestran precisamente los beneficios de su uso, de una manera principalmente divorciado de la teoría asociada.

    Aquí dedicaremos muy poco tiempo a discutir los conceptos académicos que rodean la inyección de dependencia, ya que la mayor parte de esa explicación se reservará para el segundo artículo de esta serie. De hecho, se pueden escribir y se han escrito libros enteros que proporcionan un tratamiento más profundo y riguroso de los conceptos.

    Aquí, comenzaremos con una explicación simple, pasaremos a algunos ejemplos más del mundo real y luego discutiremos información general. Otro artículo (que seguirá a este) analizará cómo la inyección de dependencia encaja en el ecosistema general de aplicación de patrones arquitectónicos de mejores prácticas.

    Una explicación sencilla

    "Inyección de dependencia" es un término demasiado complejo para un concepto extremadamente simple. En este punto, algunas preguntas sabias y razonables serían "¿cómo se define 'dependencia'?", "¿qué significa que una dependencia sea 'inyectada'?", "¿se pueden inyectar dependencias de diferentes maneras?" y "¿por qué es esto útil?" Quizás no crea que un término como "Inyección de dependencia" pueda explicarse en dos fragmentos de código y un par de palabras, pero, por desgracia, sí puede.

    La forma más sencilla de explicar el concepto es mostrárselo.

    Esto, por ejemplo, no es una inyección de dependencia:

    import { Engine } from './Engine';class Car { private engine: Engine; public constructor () { this.engine = new Engine(); } public startEngine(): void { this.engine.fireCylinders(); }}

    Pero esta es la inyección de dependencia:

    import { Engine } from './Engine';class Car { private engine: Engine; public constructor (engine: Engine) { this.engine = engine; } public startEngine(): void { this.engine.fireCylinders(); }}

    Hecho. Eso es todo. Fresco. El fin.

    ¿Qué cambió? En lugar de permitir que la Carclase cree una instancia Engine(como lo hizo en el primer ejemplo), en el segundo ejemplo, Carse Enginepasó (o se inyectó ) una instancia desde algún nivel superior de control a su constructor. Eso es todo. En esencia, esto es todo lo que es la inyección de dependencia: el acto de inyectar (pasar) una dependencia a otra clase o función. Cualquier otra cosa que involucre la noción de inyección de dependencia es simplemente una variación de este concepto simple y fundamental. Dicho de manera trivial, la inyección de dependencia es una técnica mediante la cual un objeto recibe otros objetos de los que depende, llamados dependencias, en lugar de crearlos él mismo.

     

    En general, para definir qué es una “dependencia”, si alguna clase Ausa la funcionalidad de una clase B, entonces Bes una dependencia de A, o, en otras palabras, Atiene una dependencia de B. Por supuesto, esto no se limita a las clases y también se aplica a las funciones. En este caso, la clase Cardepende de la Engineclase o Enginees una dependencia de Car. Las dependencias son simplemente variables, como la mayoría de las cosas en programación.

    La inyección de dependencia se usa ampliamente para admitir muchos casos de uso, pero quizás el uso más evidente sea permitir pruebas más sencillas. En el primer ejemplo, no podemos burlarnos fácilmente engineporque la Carclase lo crea una instancia. Siempre se utiliza el motor real. Pero, en el último caso, tenemos control sobre lo Engineque se utiliza, lo que significa que, en una prueba, podemos subclasificar Enginey anular sus métodos.

    Por ejemplo, si quisiéramos ver qué Car.startEngine()ocurre si engine.fireCylinders()se produce un error, simplemente podríamos crear una FakeEngineclase, hacer que la extienda Enginey luego anularla fireCylinderspara que arroje un error. En la prueba, podemos inyectar ese FakeEngineobjeto en el constructor de Car. Dado que FakeEngine es Engine por implicación de herencia, se cumple el sistema de tipos TypeScript. Usar herencia y anulación de métodos no sería necesariamente la mejor manera de hacerlo, como veremos más adelante, pero ciertamente es una opción.

    Quiero dejar muy, muy claro que lo que ves arriba es la noción central de inyección de dependencia. A Car, por sí solo, no es lo suficientemente inteligente como para saber qué motor necesita. Sólo los ingenieros que construyen el coche entienden los requisitos de sus motores y ruedas. Por lo tanto, tiene sentido que las personas que construyen el automóvil proporcionen el motor específico requerido, en lugar de dejar que uno Carmismo elija el motor que quiera usar.

    Utilizo la palabra "construir" específicamente porque el auto se construye llamando al constructor, que es donde se inyectan las dependencias. Si el automóvil también creó sus propios neumáticos además del motor, ¿cómo sabemos que los neumáticos que se utilizan son seguros para girar a las RPM máximas que el motor puede generar? Por todas estas razones y más, debería tener sentido, tal vez intuitivamente, que Carno tenga nada que ver con decidir qué Enginey para qué Wheelsse utiliza. Deberían proporcionarse desde algún nivel superior de control.

     

    En el último ejemplo que muestra la inyección de dependencia en acción, si imagina Engineque es una clase abstracta en lugar de una concreta, esto debería tener aún más sentido: el automóvil sabe que necesita un motor y sabe que el motor debe tener alguna funcionalidad básica. pero cómo se gestiona ese motor y cuál es su implementación específica está reservado para ser decidido y proporcionado por el fragmento de código que crea (construye) el automóvil.

    Un ejemplo del mundo real

    Vamos a ver algunos ejemplos prácticos más que esperamos ayuden a explicar, nuevamente de manera intuitiva, por qué la inyección de dependencia es útil. Con suerte, al no insistir en lo teórico y, en cambio, pasar directamente a conceptos aplicables, se podrán ver más plenamente los beneficios que proporciona la inyección de dependencia y las dificultades de la vida sin ella. Más adelante volveremos a un tratamiento un poco más “académico” del tema.

    Comenzaremos construyendo nuestra aplicación normalmente, de una manera altamente acoplada, sin utilizar inyección de dependencia o abstracciones, para que podamos ver las desventajas de este enfoque y la dificultad que agrega a las pruebas. A lo largo del camino, refactorizaremos gradualmente hasta que rectifiquemos todos los problemas.

    Para comenzar, supongamos que se le ha asignado la tarea de crear dos clases: un proveedor de correo electrónico y una clase para una capa de acceso a datos que debe ser utilizada por algunos UserService. Comenzaremos con el acceso a los datos, pero ambos se definen fácilmente:

    // UserRepository.tsimport { dbDriver } from 'pg-driver';export class UserRepository { public async addUser(user: User): Promisevoid { // ... dbDriver.save(...) } public async findUserById(id: string): PromiseUser { // ... dbDriver.query(...) } public async existsByEmail(email: string): Promiseboolean { // ... dbDriver.save(...) }}

    Nota: El nombre "Repositorio" aquí proviene del "Patrón de repositorio", un método para desacoplar su base de datos de su lógica empresarial. Puede obtener más información sobre el patrón de repositorio , pero para los fines de este artículo, simplemente puede considerarlo como una clase que encapsula su base de datos de modo que, según la lógica empresarial, su sistema de almacenamiento de datos se trate simplemente como un sistema en memoria. recopilación. Explicar completamente el patrón de repositorio está fuera del alcance de este artículo.

    Así es como normalmente esperamos que funcionen las cosas y dbDriverestá codificado dentro del archivo.

     

    En su UserService, importaría la clase, crearía una instancia y comenzaría a usarla:

    import { UserRepository } from './UserRepository.ts';class UserService { private readonly userRepository: UserRepository; public constructor () { // Not dependency injection. this.userRepository = new UserRepository(); } public async registerUser(dto: IRegisterUserDto): Promisevoid { // User object validation const user = User.fromDto(dto); if (await this.userRepository.existsByEmail(dto.email)) return Promise.reject(new DuplicateEmailError()); // Database persistence await this.userRepository.addUser(user); // Send a welcome email // ... } public async findUserById(id: string): PromiseUser { // No need for await here, the promise will be unwrapped by the caller. return this.userRepository.findUserById(id); }}

    Una vez más, todo sigue normal.

    Un breve comentario: un DTO es un objeto de transferencia de datos: es un objeto que actúa como una bolsa de propiedades para definir una forma de datos estandarizada a medida que se mueve entre dos sistemas externos o dos capas de una aplicación. Puede obtener más información sobre las DTO en el artículo de Martin Fowler sobre el tema, aquí . En este caso, IRegisterUserDtodefine un contrato sobre cuál debe ser la forma de los datos tal como provienen del cliente. Solo tengo dos propiedades: idy email. Podría pensar que es peculiar que el DTO que esperamos del cliente para crear un nuevo usuario contenga el ID del usuario aunque aún no hayamos creado un usuario. El ID es un UUID y permito que el cliente lo genere por diversos motivos, que están fuera del alcance de este artículo. Además, la findUserByIdfunción debería asignar el Userobjeto a un DTO de respuesta, pero lo descuidé por brevedad. Finalmente, en el mundo real, no haría que un Usermodelo de dominio contuviera un fromDtométodo. Eso no es bueno para la pureza del dominio. Una vez más, su propósito aquí es la brevedad.

    A continuación, desea gestionar el envío de correos electrónicos. Una vez más, como de costumbre, simplemente puede crear una clase de proveedor de correo electrónico e importarla a su archivo UserService.

    // SendGridEmailProvider.tsimport { sendMail } from 'sendgrid';export class SendGridEmailProvider { public async sendWelcomeEmail(to: string): Promisevoid { // ... await sendMail(...); }}

    Dentro UserService:

    import { UserRepository } from './UserRepository.ts';import { SendGridEmailProvider } from './SendGridEmailProvider.ts';class UserService { private readonly userRepository: UserRepository; private readonly sendGridEmailProvider: SendGridEmailProvider; public constructor () { // Still not doing dependency injection. this.userRepository = new UserRepository(); this.sendGridEmailProvider = new SendGridEmailProvider(); } public async registerUser(dto: IRegisterUserDto): Promisevoid { // User object validation const user = User.fromDto(dto); if (await this.userRepository.existsByEmail(dto.email)) return Promise.reject(new DuplicateEmailError()); // Database persistence await this.userRepository.addUser(user); // Send welcome email await this.sendGridEmailProvider.sendWelcomeEmail(user.email); } public async findUserById(id: string): PromiseUser { return this.userRepository.findUserById(id); }}

    Ahora tenemos una clase totalmente trabajadora, y en un mundo donde no nos importa la capacidad de prueba o escribir código limpio según cualquier definición, y en un mundo donde la deuda técnica es inexistente y los molestos administradores de programas no lo hacen. No establezco plazos, esto está perfectamente bien. Desafortunadamente, ese no es un mundo en el que tenemos el beneficio de vivir.

     

    ¿Qué sucede cuando decidimos que necesitamos migrar de SendGrid para correos electrónicos y usar MailChimp en su lugar? De manera similar, ¿qué sucede cuando queremos realizar una prueba unitaria de nuestros métodos? ¿Vamos a utilizar la base de datos real en las pruebas? Peor aún, ¿realmente vamos a enviar correos electrónicos reales a direcciones de correo electrónico potencialmente reales y pagar por ello también?

    En el ecosistema tradicional de JavaScript, los métodos de las clases de prueba unitaria bajo esta configuración están plagados de complejidad y exceso de ingeniería. La gente incorpora bibliotecas enteras simplemente para proporcionar funcionalidad de trozos, lo que agrega todo tipo de capas de indirección y, peor aún, puede acoplar directamente las pruebas a la implementación del sistema bajo prueba, cuando, en realidad, las pruebas nunca deberían saber cómo. el sistema real funciona (esto se conoce como prueba de caja negra). Trabajaremos para mitigar estos problemas mientras analizamos cuál UserServicees la responsabilidad real y aplicamos nuevas técnicas de inyección de dependencia.

    Consideremos por un momento lo que UserServicehace a. El objetivo de la existencia de UserServicees ejecutar casos de uso específicos que involucren a los usuarios: registrarlos, leerlos, actualizarlos, etc. Es una buena práctica que las clases y funciones tengan una sola responsabilidad (SRP, el principio de responsabilidad única), y la responsabilidad de UserServicees manejar las operaciones relacionadas con el usuario. ¿ Por qué entonces es UserServiceresponsable de controlar la vida útil de UserRepositoryy SendGridEmailProvideren este ejemplo? Noticias raras y curiosas

    Imagínese si tuviéramos alguna otra clase utilizada UserServiceque abriera una conexión de larga duración. ¿También debería UserServiceser responsable de deshacerse de esa conexión? Por supuesto que no. Todas estas dependencias tienen una vida útil asociada: podrían ser singletons, podrían ser transitorias y tener como alcance una solicitud HTTP específica, etc. El control de estas vidas está fuera del alcance de UserService. Entonces, para resolver estos problemas, inyectaremos todas las dependencias, tal como vimos antes.

    import { UserRepository } from './UserRepository.ts';import { SendGridEmailProvider } from './SendGridEmailProvider.ts';class UserService { private readonly userRepository: UserRepository; private readonly sendGridEmailProvider: SendGridEmailProvider; public constructor ( userRepository: UserRepository, sendGridEmailProvider: SendGridEmailProvider ) { // Yay! Dependencies are injected. this.userRepository = userRepository; this.sendGridEmailProvider = sendGridEmailProvider; } public async registerUser(dto: IRegisterUserDto): Promisevoid { // User object validation const user = User.fromDto(dto); if (await this.userRepository.existsByEmail(dto.email)) return Promise.reject(new DuplicateEmailError()); // Database persistence await this.userRepository.addUser(user); // Send welcome email await this.sendGridEmailProvider.sendWelcomeEmail(user.email); } public async findUserById(id: string): PromiseUser { return this.userRepository.findUserById(id); }}

    ¡Excelente! Ahora UserServicerecibe objetos creados previamente, y cualquier fragmento de código que llame y cree uno nuevo UserServicees el fragmento de código encargado de controlar la vida útil de las dependencias. Hemos invertido el control hacia UserServiceun nivel superior. Si solo quisiera mostrar cómo podemos inyectar dependencias a través del constructor para explicar el inquilino básico de la inyección de dependencias, podría detenerme aquí. Sin embargo, todavía hay algunos problemas desde una perspectiva de diseño que, cuando se rectifiquen, servirán para hacer que nuestro uso de la inyección de dependencia sea aún más poderoso.

     

    En primer lugar, ¿por qué UserServicesabe que utilizamos SendGrid para los correos electrónicos? En segundo lugar, ambas dependencias están en clases concretas: lo concreto UserRepositoryy lo concreto SendGridEmailProvider. Esta relación es demasiado rígida: estamos atrapados al tener que pasar algún objeto que es a UserRepositoryy es a SendGridEmailProvider.

    Esto no es genial porque queremos UserServiceser completamente agnósticos con respecto a la implementación de sus dependencias. Al ser UserServiceciegos de esa manera, podemos intercambiar las implementaciones sin afectar el servicio en absoluto; esto significa que, si decidimos migrar fuera de SendGrid y usar MailChimp, podemos hacerlo. También significa que si queremos falsificar el proveedor de correo electrónico para realizar pruebas, también podemos hacerlo.

    Lo que sería útil es si pudiéramos definir alguna interfaz pública y forzar que las dependencias entrantes respeten esa interfaz, sin dejar de UserServiceser independientes de los detalles de implementación. Dicho de otra manera, debemos forzar UserServicea depender solo de una abstracción de sus dependencias, y no de sus dependencias concretas reales. Podemos hacerlo a través de interfaces.

    Comience definiendo una interfaz para UserRepositorye impleméntela:

    // UserRepository.tsimport { dbDriver } from 'pg-driver';export interface IUserRepository { addUser(user: User): Promisevoid; findUserById(id: string): PromiseUser; existsByEmail(email: string): Promiseboolean;}export class UserRepository implements IUserRepository { public async addUser(user: User): Promisevoid { // ... dbDriver.save(...) } public async findUserById(id: string): PromiseUser { // ... dbDriver.query(...) } public async existsByEmail(email: string): Promiseboolean { // ... dbDriver.save(...) }}

    Y definir uno para el proveedor de correo electrónico, implementándolo también:

     

    // IEmailProvider.tsexport interface IEmailProvider { sendWelcomeEmail(to: string): Promisevoid;}// SendGridEmailProvider.tsimport { sendMail } from 'sendgrid';import { IEmailProvider } from './IEmailProvider';export class SendGridEmailProvider implements IEmailProvider { public async sendWelcomeEmail(to: string): Promisevoid { // ... await sendMail(...); }}

    Nota: Este es el patrón adaptador de la Banda de los Cuatro Patrones de Diseño.

    Ahora, UserServicepodemos depender de las interfaces en lugar de las implementaciones concretas de las dependencias:

    import { IUserRepository } from './UserRepository.ts';import { IEmailProvider } from './SendGridEmailProvider.ts';class UserService { private readonly userRepository: IUserRepository; private readonly emailProvider: IEmailProvider; public constructor ( userRepository: IUserRepository, emailProvider: IEmailProvider ) { // Double yay! Injecting dependencies and coding against interfaces. this.userRepository = userRepository; this.emailProvider = emailProvider; } public async registerUser(dto: IRegisterUserDto): Promisevoid { // User object validation const user = User.fromDto(dto); if (await this.userRepository.existsByEmail(dto.email)) return Promise.reject(new DuplicateEmailError()); // Database persistence await this.userRepository.addUser(user); // Send welcome email await this.emailProvider.sendWelcomeEmail(user.email); } public async findUserById(id: string): PromiseUser { return this.userRepository.findUserById(id); }}

    Si las interfaces son nuevas para usted, esto puede parecer muy, muy complejo. De hecho, el concepto de crear software débilmente acoplado también puede ser nuevo para usted. Piense en los receptáculos de pared. Puede enchufar cualquier dispositivo en cualquier tomacorriente siempre que el enchufe encaje en el tomacorriente. Eso es un acoplamiento flojo en acción. Su tostadora no está conectada a la pared, porque si lo estuviera y decide actualizarla, no tendrá suerte. En su lugar, se utilizan salidas, y la salida define la interfaz. De manera similar, cuando conecta un dispositivo electrónico a su toma de corriente, no le preocupa el potencial de voltaje, el consumo máximo de corriente, la frecuencia de CA, etc., solo le importa si el enchufe encaja en el tomacorriente. Podrías pedirle a un electricista que cambie todos los cables detrás de ese tomacorriente y no tendrás ningún problema para enchufar tu tostadora, siempre y cuando ese tomacorriente no cambie. Además, su fuente de electricidad podría cambiarse para que provenga de la ciudad o de sus propios paneles solares y, una vez más, no le importa siempre y cuando todavía pueda enchufarse a ese tomacorriente.

    La interfaz es la salida y proporciona funcionalidad "plug-and-play". En este ejemplo, el cableado en la pared y la fuente de electricidad son similares a las dependencias y su tostadora es similar a UserService(depende de la electricidad): la fuente de electricidad puede cambiar y la tostadora aún funciona bien y no necesita ser tocado, porque la salida, que actúa como interfaz, define el medio estándar para que ambos se comuniquen. De hecho, se podría decir que el tomacorriente actúa como una “abstracción” del cableado de la pared, los disyuntores, la fuente eléctrica, etc.

     

    Es un principio común y bien considerado del diseño de software, por las razones anteriores, codificar en base a interfaces (abstracciones) y no a implementaciones, que es lo que hemos hecho aquí. Al hacerlo, tenemos la libertad de intercambiar implementaciones como queramos, ya que esas implementaciones están ocultas detrás de la interfaz (al igual que el cableado de la pared está oculto detrás del tomacorriente), por lo que la lógica empresarial que utiliza la dependencia nunca tiene que cambiar siempre y cuando la interfaz nunca cambie. Recuerde, UserServicesolo necesita saber qué funcionalidad ofrecen sus dependencias , no cómo se admite esa funcionalidad detrás de escena . Por eso funciona el uso de interfaces.

    Estos dos cambios simples de utilizar interfaces e inyectar dependencias marcan la diferencia en el mundo cuando se trata de crear software débilmente acoplado y resuelven todos los problemas con los que nos encontramos anteriormente.

    Si mañana decidimos que queremos confiar en Mailchimp para los correos electrónicos, simplemente creamos una nueva clase de Mailchimp que respeta la IEmailProviderinterfaz y la inyectamos en lugar de SendGrid. La UserServiceclase real nunca tiene que cambiar a pesar de que acabamos de realizar un cambio enorme en nuestro sistema al cambiar a un nuevo proveedor de correo electrónico. La belleza de estos patrones es que UserServicepermanece felizmente inconsciente de cómo funcionan detrás de escena las dependencias que utiliza. La interfaz sirve como límite arquitectónico entre ambos componentes, manteniéndolos adecuadamente desacoplados.

    Además, cuando se trata de pruebas, podemos crear falsificaciones que respeten las interfaces e inyectarlas en su lugar. Aquí puede ver un repositorio falso y un proveedor de correo electrónico falso.

    // Both fakes:class FakeUserRepository implements IUserRepository { private readonly users: User[] = []; public async addUser(user: User): Promisevoid { this.users.push(user); } public async findUserById(id: string): PromiseUser { const userOrNone = this.users.find(u = u.id === id); return userOrNone ? Promise.resolve(userOrNone) : Promise.reject(new NotFoundError()); } public async existsByEmail(email: string): Promiseboolean { return Boolean(this.users.find(u = u.email === email)); } public getPersistedUserCount = () = this.users.length;}class FakeEmailProvider implements IEmailProvider { private readonly emailRecipients: string[] = []; public async sendWelcomeEmail(to: string): Promisevoid { this.emailRecipients.push(to); } public wasEmailSentToRecipient = (recipient: string) = Boolean(this.emailRecipients.find(r = r === recipient));}

    Tenga en cuenta que ambas falsificaciones implementan las mismas interfaces que UserServiceespera que respeten sus dependencias. Ahora, podemos pasar estas falsificaciones en UserServicelugar de las clases reales y UserServiceno nos daremos cuenta; los usará como si fueran auténticos. La razón por la que puede hacer esto es porque sabe que todos los métodos y propiedades que quiere usar en sus dependencias realmente existen y son accesibles (porque implementan las interfaces), lo cual es todo lo que necesita UserServicesaber (es decir, no cómo las dependencias funcionan).

     

    Inyectaremos estos dos durante las pruebas, y esto hará que el proceso de prueba sea mucho más fácil y directo de lo que podría estar acostumbrado cuando se trata de bibliotecas de burlas y stubping exageradas, trabajando con el propio sistema interno de Jest. herramientas o intentar hacer parches.

    Aquí hay pruebas reales usando falsificaciones:

    // Fakeslet fakeUserRepository: FakeUserRepository;let fakeEmailProvider: FakeEmailProvider;// SUTlet userService: UserService;// We want to clean out the internal arrays of both fakes // before each test.beforeEach(() = { fakeUserRepository = new FakeUserRepository(); fakeEmailProvider = new FakeEmailProvider(); userService = new UserService(fakeUserRepository, fakeEmailProvider);});// A factory to easily create DTOs.// Here, we have the optional choice of overriding the defaults// thanks to the built in `Partial` utility type of TypeScript.function createSeedRegisterUserDto(opts?: PartialIRegisterUserDto): IRegisterUserDto { return { id: 'someId', email: '[email protected]', ...opts };}test('should correctly persist a user and send an email', async () = { // Arrange const dto = createSeedRegisterUserDto(); // Act await userService.registerUser(dto); // Assert const expectedUser = User.fromDto(dto); const persistedUser = await fakeUserRepository.findUserById(dto.id); const wasEmailSent = fakeEmailProvider.wasEmailSentToRecipient(dto.email); expect(persistedUser).toEqual(expectedUser); expect(wasEmailSent).toBe(true);});test('should reject with a DuplicateEmailError if an email already exists', async () = { // Arrange const existingEmail = '[email protected]'; const dto = createSeedRegisterUserDto({ email: existingEmail }); const existingUser = User.fromDto(dto); await fakeUserRepository.addUser(existingUser); // Act, Assert await expect(userService.registerUser(dto)) .rejects.toBeInstanceOf(DuplicateEmailError); expect(fakeUserRepository.getPersistedUserCount()).toBe(1);});test('should correctly return a user', async () = { // Arrange const user = User.fromDto(createSeedRegisterUserDto()); await fakeUserRepository.addUser(user); // Act const receivedUser = await userService.findUserById(user.id); // Assert expect(receivedUser).toEqual(user);});

    Aquí notarás algunas cosas: Las falsificaciones escritas a mano son muy simples. No hay complejidad en los marcos burlones que sólo sirven para ofuscar. Todo está hecho a mano y eso significa que no hay magia en el código base. El comportamiento asincrónico se simula para que coincida con las interfaces. Utilizo async/await en las pruebas a pesar de que todo el comportamiento es sincrónico porque siento que coincide más con cómo espero que funcionen las operaciones en el mundo real y porque al agregar async/await, puedo ejecutar este mismo conjunto de pruebas. También contra implementaciones reales además de las falsificaciones, por lo que se req






    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Una introducción práctica a la inyección de dependencia

    Una introducción práctica a la inyección de dependencia

    Diseño de arquitectura de componentes de interfaz de usuario y tokens, con Nathan Curtis Listas de verificación de diseño de interfaz inteligente Índice

    programar

    es

    https://aprendeprogramando.es/static/images/programar-una-introduccion-practica-a-la-inyeccion-de-dependencia-1079-0.jpg

    2024-05-21

     

    Una introducción práctica a la inyección de dependencia
    Una introducción práctica a la inyección de dependencia

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20