67

When I tried to do unit testing for private methods in a Class getting error as private methods are only accessible inside the class. Here I added sample snippet for my class and mocha test. Kindly provide me solution to implement unit test for private methods.

Class Name: Notification.ts

class Notification {
 constructor() {}
 public validateTempalte() {
  return true;
 }

 private replacePlaceholder() {
  return true;
 }
}

Unit Test:

import {Notification} from 'Notification';
import * as chai from "chai";

describe("Notification", function(){

  describe('#validateTempalte - Validate template', function() {
      it('it should return success', function() {
        const result = new Notification()
        chai.expect(result.validateTempalte()).to.be.equal(true);
      });
    });
  describe('#replacePlaceholder - Replace Placeholder', function() {
      it('it should return success', function() {
        const result = new Notification()
        // As expected getting error "Private is only accessible within class"
        chai.expect(result.replacePlaceholder()).to.be.equal(true);
      });
    });
});

As a workaround, currently, I am changing access specifier of function replacePlaceholder to public. But I don't think its a valid approach.

Ashok JayaPrakash
  • 2,115
  • 1
  • 16
  • 22

11 Answers11

68

A possible solution to omit Typescript checks is to access the property dynamically (Not telling wether its good).

myClass['privateProp'] or for methods: myClass['privateMethod']()

denu5
  • 1,101
  • 9
  • 7
57

Technically, in current versions of TypeScript private methods are only compile-time checked to be private - so you can call them.

class Example {
    public publicMethod() {
        return 'public';
    }

    private privateMethod() {
        return 'private';
    }
}

const example = new Example();

console.log(example.publicMethod()); // 'public'
console.log(example.privateMethod()); // 'private'

I mention this only because you asked how to do it, and that is how you could do it.

Correct Answer

However, that private method must be called by some other method... otherwise it isn't called at all. If you test the behaviour of that other method, you will cover the private method in the context it is used.

If you specifically test private methods, your tests will become tightly coupled to the implementation details (i.e. a good test wouldn't need to be changed if you refactored the implementation).

Disclaimer

If you still test it at the private method level, the compiler might in the future change and make the test fail (i.e. if the compiler made the method "properly" private, or if a future version of ECMAScript added visibility keywords, etc).

MWO
  • 2,627
  • 2
  • 10
  • 25
Fenton
  • 241,084
  • 71
  • 387
  • 401
  • 32
    I find this argumentation quite often, but imo it's far from being realistic. If you have a complex function, you want to split it into smaller logical segments to test them individually, reducing development time, complexitiy, the likeliness of bugs and the tests' volume. The standard way to solve this is to move all segments as functions it into a `util` module and export them there ("They are official util methods then, aren't they?"), which just obfuscates the underlying dilemma and undermines the logic of visibility scopes. – NotX Jul 23 '20 at 10:24
  • @NotX part one of the answer covers the people that want to do it... so I'm not excluding anyone here. The reason you see the warning about tightly coupling your tests to implementation details (by defining "unit" too narrowly) is that is makes it super-hard to refactor as your tests fail due to details the tests shouldn't depend on. Testing a broader unit allows extensive changes that don't impact the tests. It makes the process more like Kent Beck's definition. I defected from your perspective in 2013 after 5 years working that way and the last 7 years have been far happier. – Fenton Jul 23 '20 at 14:56
  • 5
    I'ld say if you change implementation details at a bigger scope there's nothing wrong with just deleting the test which don't to apply to the new logic anymore. They were there to help you when you you've developed the old version of your code. Later, when you change internal stuff, you want to replace them with new tests which help you to develop the new logic correctly. Keep in mind I'm talking about complex functions here where the single segments aren't trivial. – NotX Jul 24 '20 at 17:24
  • "If you test the behaviour of that other method, you will cover the private method in the context it is used." It's called a "unit" test for a reason, you're not supposed to couple dependant functions with a unit test. "If you specifically test private methods, your tests will become tightly coupled to the implementation details" You're instead coupling the private method to the public method when you test it. Further you can still test a private method's business logic without testing things like schemas... so you can change your schema and your test won't shatter. – Sophie McCarrell Oct 26 '20 at 16:24
  • 1
    @JasonMcCarrell we have a different idea of isolation. Ian Coopers "TDD Where Did It All Go Wrong" talk changed my mind on this in 2013: https://www.stevefenton.co.uk/2013/05/my-unit-testing-epiphany/ – Fenton Oct 28 '20 at 15:19
  • This solution results in compiler errors, it should not be accepted. – Tobias Cudnik Mar 19 '21 at 08:55
  • 1
    @Fenton, thank you for sharing this! You have actually led me down a path which I believe will be very beneficial. I was previously of the opposing mindset, but coding that way was repelling. Cooper explains the inherent resistance to that style of TDD and why the teachings in the book he references allow for both speedy and secure tests. I'm hopeful this will bridge the gap for a new, better era of coding style for me (and others who may come across this conversation)! – nikojpapa Apr 14 '21 at 04:08
  • @TobiasCudnik it does encourage to not test private functions! – Ichor de Dionysos May 07 '21 at 17:48
47

In my case, I use the prototype of the object to get access to a private method. It works well and TS does not swear.

For example:

class Example {
    private privateMethod() {}
}

describe() {
    it('test', () => {
        const example = new Example();
        const exampleProto = Object.getPrototypeOf(example);

        exampleProto.privateMethod();
    })
}

If you use a static method then use exampleProto.constructor.privateMethod();.

Lauren Yim
  • 12,700
  • 2
  • 32
  • 59
andy90rus
  • 479
  • 4
  • 2
4

My subjective solution: you could define a new testing-only interface that extends the original one by adding the private methods as (implicitly public) interface methods. Then, you cast the instantiated object to this new test type. This satisfies both tsc and VS code type checking. Your example with my solution:

interface INotification {
  validateTemplate(): boolean,
}

class Notification implements INotification {
  constructor() {}

  public validateTemplate() {
    return true;
  }

  private replacePlaceholder() {
    return true;
  }
}

Testing:

import {Notification} from 'Notification';
import * as chai from "chai";

interface INotificationTest extends INotification {
  replacePlaceholder(): boolean;
}

describe("Notification", function(){

  describe('#validateTemplate - Validate template', function() {
    it('it should return success', function() {
      const result = new Notification() as INotificationTest;
      chai.expect(result.validateTemplate()).to.be.equal(true);
    });
  });
  describe('#replacePlaceholder - Replace Placeholder', function() {
    it('it should return success', function() {
      const result = new Notification() as INotificationTest;
      // Works!
      chai.expect(result.replacePlaceholder()).to.be.equal(true);
    });
  });
});

Advantages:

  • tsc and vs code do not complain
  • IntelliSense (or any other autocomplete) works
  • simple (subjectively)
  • If you don't want to define the original interface (INotification), you could just fully define the test one (INotificationTest) instead of extending and cast it in the same manner.

Disadvantages:

  • Added boilerplate
  • Need to have both of the interfaces updated and in sync
  • Potentially introducing bugs by explicitly casting as a non original type.

I leave it up to you to decide whether this is worth it or no. In my case, the positives outweigh the negatives. I have tested this with jest, but I assume that mocha.js is no different here.

Edit: but generally I would agree with Fenton's answer

Albert
  • 41
  • 4
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/31483435) – Barry Michael Doyle Apr 10 '22 at 20:43
3

In HolgerJeromin's comment, the comment issue has a succinct solution that still uses the property syntax.

The solution is to type cast your object / class to any.

Examples:

(<any>myClass).privateMethod();
const value = (<any>myClass).privateValue;
(myClass as any).privateMethod();
const value = (myClass as any).privateValue;

This method satisfies the compiler as well as the VSCode syntax highlighting.

Here are some of my notes from the issue that talks about this

  • Accessing via a string is more common, although I don't see why it might be more typesafe.
  • These features are done deliberately, therefore they are helping more than hindering.
  • There is probably a way to disable this type of feature so people don't copy and paste this code into production. "noImplicitAny": true, might help in the tsconfig.json
IcyIcicle
  • 564
  • 3
  • 15
2

Extract out the private function into a separate/stand alone function, but don't export it externally.

This is somewhat semantically correct, since after all — a private function is private and should not be accessed by anyone except the class itself.

Zorayr
  • 23,770
  • 8
  • 136
  • 129
1

Here is what I do:

  • create object which will hold target method
  • get method with object['method'] notation and assign it as named function in holder object
  • bind execution context of target method to test target
  • spy on stored method within holder object
  • call method on holder
  • create your assertion
  it('should mapToGraph', () => {
    const holder = {fn: component['mapToGraph'].bind(component)};
    const spy = jest.spyOn(holder, 'fn');
    holder.fn(mockInquiryCategory);
    expect(spy).toBeCalled();
  });

No types were harmed.

Maxime Lyakhov
  • 140
  • 1
  • 11
0
// module.ts
  private async privateMethod = () => "private method executed"
  public async testPrivateMethods(...args) {
        if (process.env.NODE_ENV === 'development') {
            return this.privateMethod(...args);
        }
    }

Now we can reach our private method to test. In jest file:

// module.spec.js
describe('Module', () => {
    let service: Module = new Module();

    it('private method should be defined', () => {
        expect(service.testPrivateMethods).toBeDefined();
    });
}

You need to set your enviroment variable name of NODE_ENV must be development.

// .env
NODE_ENV="development"
Baris Senyerli
  • 642
  • 7
  • 13
0

The fun thing is that it's just a typescript error (not javascript), so you can fix it with

// @ts-expect-error

and everything works fine.

I consider it as a legitimate solution, as the goal was to suppress typescript in this particular case.

Supervision
  • 1,683
  • 1
  • 18
  • 23
0

As the answers told here work, I do not believe they are clean and correct.

If you want (and you shall) clean code, I offer you ,,mocking".

Suppose the following example:

class CombatController implements ICombatController {
    /**
     * Protected for being able to test it
     */
    protected checkForAttackStyleAndValidate(): boolean {
        return false;
    }
}

Now, the CombatController is the class we want to test. We make checkForAttackStyleAndValidate() method protected, because we want to test it. I like to comment above the method that it is protected only for testing purposes.

Now, the actual CombatControllerTest.ts

class CombatControllerMock extends CombatController {
  public checkForAttackStyleAndValidate_pub(): boolean {
    return super.checkForAttackStyleAndValidate();
  }
}

describe() {
    it('test', () => {
        let cc: CombatControllerMock = new CombatControllerMock ();
        assert.equal(cc.checkForAttackStyleAndValidate_pub(), true);
    })
}

Discussion

Now, some may argue, that changing private method to protected is unwanted. However, you rarely use inheritance in classes, and if so, when protected is used, you can distinquish whether method is protected only for testing purposes, thanks to the comment. It will be a bit tricky to figure out that some protected method is not supposed to be called from the parent, however, all this logic is encapsulated anyway in the composite class. From the outside, you get a nice class.

Reusability

When needed, you can move the CombatControllerMock into a separate class, and re-use it in different tests. Like this, you can create complex tests for integration tests for complicated instances with multiple dependencies (for example, game objects, such as Players, Spells...)

Customizability

When needed, you can define custom behavior for a specific method, for example, when mocking an abstract class, and you never intend to call that method anyway.

class Player implements IPlayer {    
    public damage(i: int): boolean{
        return false;
    }
    public abstract giveItem(i: IInventoryItemProto): boolean;
}

So for a test where giveItem() will never be called, we mock it.

class PlayerMock extends Player { 
    public giveItem(i: IInventoryItemProto): boolean{ return false; }
}

And you can use PlayerMock in different tests aswell, creating more complex testing mock composites.

Alternative solution

As people here suggested, that you can actually call a private method, which you never shall, it may be used inside the mock _pub() method variant. However, I strongly recommend NOT to call the private method from anywhere else, than testing code, or Mock.

Jan Glaser
  • 364
  • 2
  • 14
-3

Since private methods are not accessible outside class, you can have another public method which calls replacePlaceholder() in Notification class and then test the public method.