Saltar al contenido

Cómo escribir pruebas unitarias para Angular/TypeScript para métodos privados con Jasmine

No busques más por todo internet porque llegaste al lugar perfecto, poseemos la solución que buscas pero sin problema.

Solución:

Estoy contigo, aunque es un buen objetivo “solo probar la API pública por unidad”, hay ocasiones en las que no parece tan simple y sientes que estás eligiendo entre comprometer la API o las pruebas unitarias. Ya sabes esto, ya que eso es exactamente lo que estás pidiendo hacer, así que no entraré en eso. 🙂

En TypeScript, descubrí algunas formas en que puede acceder a miembros privados para realizar pruebas unitarias. Considere esta clase:

class MyThing 

    private _name:string;
    private _count:number;

    constructor() 
        this.init("Test", 123);
    

    private init(name:string, count:number)
        this._name = name;
        this._count = count;
    

    public get name() return this._name; 

    public get count() return this._count; 


Aunque TS restringe el acceso a los miembros de la clase usando private, protected, public, el JS compilado no tiene miembros privados, ya que esto no es algo en JS. Se usa exclusivamente para el compilador TS. Por lo tanto:

  1. Puedes afirmar a any y evite que el compilador le advierta sobre las restricciones de acceso:

    (thing as any)._name = "Unit Test";
    (thing as any)._count = 123;
    (thing as any).init("Unit Test", 123);
    

    El problema con este enfoque es que el compilador simplemente no tiene idea de lo que está haciendo desde el principio. anypara que no obtenga los errores de tipo deseados:

    (thing as any)._name = 123; // wrong, but no error
    (thing as any)._count = "Unit Test"; // wrong, but no error
    (thing as any).init(0, "123"); // wrong, but no error
    

    Obviamente, esto hará que la refactorización sea más difícil.

  2. Puedes usar array acceso ([]) para llegar a los miembros privados:

    thing["_name"] = "Unit Test";
    thing["_count"] = 123;
    thing["init"]("Unit Test", 123);
    

    Si bien parece raro, TSC en realidad validará los tipos como si hubiera accedido a ellos directamente:

    thing["_name"] = 123; // type error
    thing["_count"] = "Unit Test"; // type error
    thing["init"](0, "123"); // argument error
    

    Para ser honesto, no sé por qué esto funciona. Aparentemente, esta es una “escotilla de escape” intencional para darle acceso a miembros privados sin perder la seguridad del tipo. Esto es exactamente lo que creo que desea para sus pruebas unitarias.

Aquí hay un ejemplo de trabajo en TypeScript Playground.

Editar para TypeScript 2.6

Otra opción que a algunos les gusta es usar // @ts-ignore (agregado en TS 2.6) que simplemente suprime todos los errores en la siguiente línea:

// @ts-ignore
thing._name = "Unit Test";

El problema con esto es que suprime todos los errores en la siguiente línea:

// @ts-ignore
thing._name(123).this.should.NOT.beAllowed("but it is") = window / ;

Yo personalmente considero @ts-ignore un olor a código, y como dicen los documentos:

te recomendamos usar este comentario con mucha moderación. [emphasis original]

Puedes llamar a métodos privados..

Si te encuentras con el siguiente error:

expect(new FooBar(/*...*/).initFooBar()).toEqual(/*...*/);
// TS2341: Property 'initFooBar' is private and only accessible within class 'FooBar'

Solo usa // @ts-ignore:

// @ts-ignore
expect(new FooBar(/*...*/).initFooBar()).toEqual(/*...*/);

Como la mayoría de los desarrolladores no recomiendo probar la función privada¿Por qué no probarlo?.

P.ej.

TuClase.ts

export class FooBar 
  private _status: number;

  constructor( private foo : Bar ) 
    this.initFooBar();
  

  private initFooBar(data)
    this.foo.bar( data );
    this._status = this.foo.foo();
  

TestYourClass.spec.ts

describe("Testing foo bar for status being set", function() 

...

//Variable with type any
let fooBar;

fooBar = new FooBar();

...
//Method 1
//Now this will be visible
fooBar.initFooBar();

//Method 2
//This doesn't require variable with any type
fooBar['initFooBar'](); 
...

Gracias a @Aaron, @Thierry Templier.

Comentarios y valoraciones del tutorial

Tienes la opción de animar nuestro quehacer ejecutando un comentario y valorándolo te lo agradecemos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *