119

is it possible to use Jasmine unit testing framework's spyon method upon a classes private methods?

The documentation gives this example but can this be flexible for a private function?

describe("Person", function() {
    it("calls the sayHello() function", function() {
        var fakePerson = new Person();
        spyOn(fakePerson, "sayHello");
        fakePerson.helloSomeone("world");
        expect(fakePerson.sayHello).toHaveBeenCalled();
    });
});
xav
  • 5,452
  • 7
  • 48
  • 57
user502014
  • 2,241
  • 5
  • 24
  • 32

13 Answers13

203

Just add a generic parameter < any> to the spyon() function:

 spyOn<any>(fakePerson, 'sayHello');

It works on perfectly !

Luillyfe
  • 6,183
  • 8
  • 36
  • 46
  • 1
    This should be the correct answer. This is what I do all the time. – Samuel Thompson Apr 09 '18 at 19:53
  • 3
    I tried this solution and it works perfectly well. Additionally, a private field can be accessed using array index notation, like I mentioned earlier on. – Koji D'infinte May 23 '18 at 22:04
  • 14
    Neither of those worked for me. You can't access via array notation since the spyOn takes two arguments. Putting as shown also throws an error of the wrong number of arguments. This is what worked for me: `spyOn(fakePerson as any, 'sayHello');` – Russ Jun 15 '18 at 18:58
  • 3
    I do this as well. Is there a better way without being so 'generic' using any? I tried for example spyOn(fakePerson, 'sayHello'); But then still complains about 'say hello'. IS it possible something like spyOn or something? For a bit better context? – L1ghtk3ira Nov 23 '18 at 18:59
  • 4
    Can someone also explain **why** this solution works? What exactly does adding `` do so that this works? – risingTide Mar 07 '19 at 18:03
  • 14
    @risingTide Adding `` drops the type checking, so there'll be no TypeScript compile-time errors (and no errors in your editor). But the TypeSciprt ultimately gets compiled into Javascript where every method is public, so this will work to drop the Typescript errors. – WindUpDurb Mar 20 '19 at 17:51
  • 1
    @WindUpDurb Gotcha...that makes sense. Thank you. – risingTide Mar 20 '19 at 18:14
36
spyOn<any>(fakePerson, 'sayHello');
expect(fakePerson['sayHello']).toHaveBeenCalled();

by adding <any> to spyOn you remove it from typescript type check. you also need to use array index notation in order to access a private method (sayHello) in the test expect

omer
  • 2,435
  • 2
  • 24
  • 28
  • 1
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – Igor F. Feb 19 '20 at 13:41
18

Let say sayHello(text: string) is a private method. You can use the following code:

// Create a spy on it using "any"
spyOn<any>(fakePerson, 'sayHello').and.callThrough();

// To access the private (or protected) method use [ ] operator:
expect(fakeperson['sayHello']).toHaveBeenCalledWith('your-params-to-sayhello');
  • Create a spy on private method using any.
  • To access the private (or protected) method use [] operator.
A-Sharabiani
  • 17,750
  • 17
  • 113
  • 128
  • Down voted because you didn't address the main concern which is how do you spy on a private method. This code is not all that different than what was originally provided in the question. – JeffryHouser Oct 18 '19 at 19:13
  • @JeffryHouser The `sayHello` is the a private method. and the first line is how to create a spy on it. The second line is the test. – A-Sharabiani Oct 18 '19 at 19:50
  • Got it; your answer is missing the explanation of how to spy on a private method--AKA using the `` tag. That is why I got confused. – JeffryHouser Oct 19 '19 at 20:06
10

if you use Typescript for your objects, the function isn't really private.
All you need is to save the value that returned from spyOn call and then query it's calls property.

At the end this code should work fine for you (at least it worked for me):

describe("Person", function() {
    it("calls the sayHello() function", function() {
        var fakePerson = new Person();
        // save the return value:
        var spiedFunction = spyOn<any>(fakePerson, "sayHello");
        fakePerson.helloSomeone("world");
        // query the calls property:
        expect(spiedFunction.calls.any()).toBeFalsy();
    });
});
aWebDeveloper
  • 36,687
  • 39
  • 170
  • 242
jurl
  • 2,504
  • 1
  • 17
  • 20
  • 4
    I get a type error if I try to call a non-exported (private) function: `Error:(33, 56) TS2345:Argument of type '"sayHello"' is not assignable to parameter of type '"sayGoodbye"'.` where `sayGoodbye` is a public method on `Person` and `sayGoodbye` is private. I have to cast it to any ("sayHello" as any) – FlavorScape May 09 '17 at 17:40
  • I need more context here, it seems like your assignment is not working and not the access to the private function. But try to access it like this: `person["sayHello"]` instead of `person.sayHello` (if that's what you are doing). This is not best practice, but in rare cases is forgiven ;) – jurl May 10 '17 at 19:09
  • Agreed with @FlavorScape. Typescript (at least 1.7 and up) expects the spied-on finction to be public, and since sayHello is not type sayGoodbye (or any of the other public functions) it will throw an error. I've only been able to fix this using the spy listed above. – Beartums May 10 '18 at 05:38
  • 1
    It seems like things have changed since my last comment. `spy` may indeed be the right answer. Thanks – jurl May 10 '18 at 09:56
  • `spy` is a revelation. – Neurotransmitter Feb 12 '21 at 12:02
6
spyOn(fakePerson, <never>'sayHello');
spyOn(fakePerson, <keyof Person>'sayHello');

Both silence the type error and don't interfere with TSLint's no-any rule.

mlntdrv
  • 137
  • 1
  • 8
5

In my case (Typescript):

jest.spyOn<any, string>(authService, 'isTokenActual')

OR with mocked result:

jest.spyOn<any, string>(authService, 'isTokenActual').mockImplementation(() => {
  return false;
});
user3765649
  • 304
  • 4
  • 11
3

There is an update to the context of this question, in regard to what "private" means. As of ES2022, JavaScript has private class fields, that are prefixed with a hash (#). These properties are truly private, and even with the solutions suggested, cannot be spyOn()ed.

Braden.Biz
  • 109
  • 1
  • 9
1

Typescript gets compiled to javascript and in javascript every method is public. So you can use array index notation to access private methods or fileds, viz:

Object['private_field']
Koji D'infinte
  • 1,309
  • 12
  • 20
0

When your class looks like this

class SomeClass {
    private method(){}
}

In test you can do

// @ts-ignore
class SomeClassForTest extends SomeClass {
    public method(){}
}

let service: SomeClassForTest = TestBed.inject(SomeClass) as any;

IMHO it is better than using , because you dont have to do it on each line.

prespic
  • 1,635
  • 1
  • 17
  • 20
0
fakePerson['sayHello'] = jasmine.createSpy().and.callThrough();
expect(fakePerson['sayHello']).toHaveBeenCalled();
Syscall
  • 19,327
  • 10
  • 37
  • 52
Everton Sales
  • 311
  • 3
  • 3
0

If you want to test private functions within a class, why not add a constructor to your class that signals that those private functions get returned?

Have a read through this to see what I mean: http://iainjmitchell.com/blog/?p=255

I have been using a similar idea and so far its working out great!

StevenMcD
  • 17,262
  • 11
  • 42
  • 54
  • 5
    If you publish your privat method it's not privat anymore. Btw, as I described in my answer it make not much sense to test privat method. – Andreas Köberle Dec 13 '11 at 11:10
  • 1
    We can agree is disagree on that. Our javascript codebase is massive and we only expose a handleful of public functions/properties on some of our classes. A lot of logic is handled in those private functions. I only public a private method so the testing framework has access to it. If the constructor is not called correctly then the private function is not returned. – StevenMcD Dec 13 '11 at 11:36
-2

No cause you cant access a private function outside the context of your instance.

Btw, its not a good idea to spy on objects you wanna test. When you test if a specific method in your class you want to test is called, it says nothing. Lets say you wrote the test and it passed, two weeks later you refactor some stuff in the function and add a bug. So your test is still green cause you the function called. B

Spies are useful when you work with Dependency Injection, where all external dependencies are passed by the constructor and not created in your class. So lets say you have a class that needs a dom element. Normaly you would use a jquery selector in the class to get this element. But how you wanna test that something is done with that element? Sure you can add it to your tests pages html. But you can also call your class passing the element in the constructor. Doing so, you can use spy to check if your class interacts with that element as you expected.

Andreas Köberle
  • 106,652
  • 57
  • 273
  • 297
  • 31
    It's not correct to say 'its not a good idea to spy on objects you wanna test`. Use of spies is not limited to checking whether not a function was simply called and that's it. You can use spies to check returned values, to replace functions entirely for test cases, throwing errors, etc. I would suggest reading the jasmine documentation for a more complete understanding. – Tim McClure Sep 23 '15 at 18:13
  • 2
    its not a good idea to spy !?? you cant be more wrong! yeap i agree with Tim and you should look at the doco, whos voted this up?! – AltF4_ Feb 21 '17 at 11:21
  • 3
    Thats not what I said. Its ok to use spies. But you should handle the object you wanna test as a black box. Only test what goes in and out. Don't test internals of the black box, what you would do if you spy on methods of the object under test. So spy on on callbacks that you pass into the object is totally fine. – Andreas Köberle Feb 21 '17 at 11:34
  • It's exactly the opposite. You are testing "unit", you are not interested what other "unit" is doing. You will have test for other "unit". – Slaven Tomac Jun 17 '21 at 19:57
-3
const spy = spyOn<any>(component, 'privateMethod');
expect(spy).toHaveBeenCalled();

To avoid lint warnings regarding object access via string literals, create a local constant of the spy object.

OOP
  • 293
  • 4
  • 16