Alguma maneira de modificar espiões Jasmine com base em argumentos?

147

Eu tenho uma função que eu gostaria de testar, que chama um método de API externo duas vezes, usando parâmetros diferentes. Gostaria de zombar dessa API externa com um espião Jasmine e retornar coisas diferentes com base nos parâmetros. Existe alguma maneira de fazer isso em Jasmine? O melhor que posso apresentar é um hack usando o andCallFake:

var functionToTest = function() {
  var userName = externalApi.get('abc');
  var userId = externalApi.get('123');
};


describe('my fn', function() {
  it('gets user name and ID', function() {
    spyOn(externalApi, 'get').andCallFake(function(myParam) {
      if (myParam == 'abc') {
        return 'Jane';
      } else if (myParam == '123') {
        return 98765;
      }
    });
  });
});
Jmr
fonte

Respostas:

215

Nas versões Jasmine 3.0 e superior, você pode usar withArgs

describe('my fn', function() {
  it('gets user name and ID', function() {
    spyOn(externalApi, 'get')
      .withArgs('abc').and.returnValue('Jane')
      .withArgs('123').and.returnValue(98765);
  });
});

Para versões do Jasmine anteriores à 3.0, callFakeé o caminho certo a seguir, mas você pode simplificá-lo usando um objeto para manter os valores de retorno

describe('my fn', function() {
  var params = {
    'abc': 'Jane', 
    '123': 98765
  }

  it('gets user name and ID', function() {
    spyOn(externalApi, 'get').and.callFake(function(myParam) {
     return params[myParam]
    });
  });
});

Dependendo da versão do Jasmine, a sintaxe é um pouco diferente:

  • 1.3.1: .andCallFake(fn)
  • 2.0: .and.callFake(fn)

Recursos:

Andreas Köberle
fonte
11
Isto é agora and.callFake- jasmine.github.io/2.2/… >
Lucy Bain
Como tive que retornar promessas diferentes, o retorno parecia um pouco diferente: return q.when (params [myParam]) ;. Caso contrário, este era um ponto na solução do meu problema. A solução dos meus sonhos seria alterar as chamadas "and.returnValue".
Bill Turner
7
parece que o jasmim deveria ter uma maneira melhor de declarar isso. Como spyOn(fake, 'method').withArgs('abc').and.returnValue('Jane')e spyOn(fake, 'method').withArgs('123').and.returnValue(98765).
Jrharshath
@jrharshath .withArgsnão está funcionando para mim em jasmim 2.0
hemkaran_raghav
1
.withArgsnão está realmente disponível - eu quis dizer que esse método faria sentido ao escrever testes.
Jrharshath
9

Você também pode usar $providepara criar um espião. E simule usando em and.returnValuesvez de and.returnValuepassar dados parametrizados.

De acordo com a documentação do Jasmine: Ao encadear o espião and.returnValues, todas as chamadas para a função retornarão valores específicos em ordem até chegar ao final da lista de valores de retorno, quando, nesse ponto, retornará indefinido para todas as chamadas subseqüentes.

describe('my fn', () => {
    beforeEach(module($provide => {
        $provide.value('externalApi', jasmine.createSpyObj('externalApi', ['get']));
    }));

        it('get userName and Id', inject((externalApi) => {
            // Given
            externalApi.get.and.returnValues('abc','123');

            // When
            //insert your condition

            // Then
            // insert the expectation                
        }));
});
akhouri
fonte
Esta é a resposta correta, uma vez que um teste deve sempre saber exatamente como um espião será chamado, e deve, portanto, apenas usar returnValuespara suportar várias chamadas
Schmuli
2
Apenas para esclarecer a resposta de akhouri: esse método só funciona quando externalApi.get.and.returnValues('abc','123')é chamado dentro da itfunção. Caso contrário, se você definir uma lista de valores, caso contrário, nunca funcionará porque a ordem na qual os testes são executados não é previsível. De fato, os testes não devem depender da ordem em que são executados.
Avi.elkharrat
0

No meu caso, eu tinha um componente que estava testando e, em seu construtor, há um serviço de configuração com um método chamado getAppConfigValue chamado duas vezes, sempre com argumentos diferentes:

constructor(private configSvc: ConfigService) {
  this.configSvc.getAppConfigValue('a_string');
  this.configSvc.getAppConfigValue('another_string');
}

Na minha especificação, eu forneci o ConfigService no TestBed da seguinte forma:

{
  provide: ConfigService,
  useValue: {
    getAppConfigValue: (key: any): any {
      if (key === 'a_string) {
        return 'a_value';
      } else if (key === 'another_string') {
        return 'another_value';
      }
    }
  } as ConfigService
}

Portanto, desde que a assinatura de getAppConfigValue seja a mesma especificada no ConfigService real, o que a função faz internamente pode ser modificado.

Guillermo
fonte