TypeScript
TypeScript copied to clipboard
Removing "private" modifier from types
[Feature suggestion] How about this (for Typescript 2.8+):
type Friend<T> = {
-private [P in keyof T]: T[P]
}
Does this remove protected too ?:)
Perhaps there is also need for the opposite, adding public for all privates ?
+public [P in keyof T]: T[P];
I suppose it is very useful feature, because it allows to work better with decorators. For me it is quite common case when you create property decorator and need access to some private or protected property, but cannot get it due to current restrictions. That's why you are forced to use any type in decorators, and it does not guarantee type-safety.
The minimal example of this kind of decorators is following:
type Public<T> = {
+public [P in keyof T]: T[P]
}
const decorate = (target: any, properyName: string) => ({
get(this: Public<Base>): any { // here instead of `Public<Base>` I use `any` for now
return this.props[propertyName];
},
set(this: Public<Base>, value: any): void {
this.props[propertyName] = value;
},
});
class Base {
private props: {[key: string]: any} = {};
@decorate
public property: string = '';
}
I could really use this too.
What kind of "feedback" is needed when it says "Awaiting feedback"? And by how many?
Without something like this, is there anyway to express a similar behavior with what there is currently available in TS? Because I would like this as well, for use in decorators.
this could also be useful for testing, to mock private methods or properties that normally should not be exposed, for example:
beforeAll(() => {
const serviceA = getServiceA(...) as Public<ServiceA>;
serviceA.tokenValidityMs = 1000;
jest.spyOn(serviceA, 'somePrivateMethod').mockResolvedValue(42);
});
I would like to see this arrive in TypeScript as well. I have a ton of unit tests that force me to cast my objects as any in order to test their private stuff. This would give back full type safety within my tests.
@TwitchBronBron in general it's considered bad practice to test private state. Only the public API surface should be tested anyway.
@ffMathy of course it's a bad practice, we all know it. But sometimes you have a field that logically should be private, with one exception. A hack is much simpler than refactoring for just one special case.
yeah, I think there should be a way to use private fields, but it should be always clear when it's used, so people don't overuse it.
Rationale / example case when it's "not that bad" practice: in tests, to temporarily change a service's variable that controls some of its behavior. It makes sense for variables that are normally private since they're set from process.env / other external sources on class instantiation, cannot change at runtime, and there is no reason to expose them to other services. Currently you can only do that with (service as any).someSetting = ..., but then it's not typechecked, so you won't notice if you make a typo or the service changes.
Is this being looked at at all? It would be nice to see this feature implemented.
Bumped into this when I was searching for a solution for a similar problem +1
Adding and removing modifiers is definitely a must
This could also help with workarounds to the lack of an override keyword like this one which currently only works on public members (when it would ideally work on protected members too).
Looks like this would also help with my use case.
Currently, in Angular the @Inject(SOME_TOKEN) decorator of constructor params is totally type-unsafe, despite having exact type information in SOME_TOKEN. I believe the reason is that it's not possible to implement a type-safe decorator for a private property initialized in a constructor like this:
constructor(@Inject(TOKEN) private myProperty: MyType)
So, @RyanCavanaugh, after 2.5 years with "Awaiting More Feedback", could you please removed this label? This feature is really a much needed thing, especially in testing.
@KostyaTretyak with only 15 upvotes in 2.5 years, removing the label would imply closing this based on how often people are encountering it
@RyanCavanaugh, as for me, you don't even have to ask anyone about it, it's obviously a necessary thing. But maybe you know how, for example, to test protected or private properties of objects?
I test private or protected properties the same way I interact with them in normal usage - through their public interface, because private/protected members are an implementation detail that shouldn't be exposed to testcases.
@RyanCavanaugh It's more than just testing... I would like access to private/protected properties for ORM reasons. Using DDD patterns for Domain classes sometimes requires using public methods to set these private properties which are then saved into and back from the database. Using query patterns for these private properties would require creating a separate class to map the private properties to making it really painful and redundant (not to mention its not typesafe).
On a side note (and I can't find the issue on this)... how many votes were required to make -readonly possible?
because private/protected members are an implementation detail that shouldn't be exposed to testcases
Exactly. And because of this there is a need to write mocks to ignore the implementation detail. And without this feature it is quite difficult to write mocks.
@RyanCavanaugh, I have an example from real life. I want to test the patchPostDraft() of the PostEditController class:
@Controller()
export class PostEditController {
constructor(
private req: Request,
private res: Response<ApiResponse<any>>,
private local: LocalizationService,
private mysqlService: MysqlService,
private config: ConfigService,
private paramsService: ParamsService,
private authService: AuthService,
private emailService: EmailService,
private postEditService: PostEditService,
private utilService: UtilService
) {}
@Route('PATCH', ':postId', [AuthGuard])
async patchPostDraft() {
const { postId } = this.req.routeParams;
this.paramsService.id(postId);
const [{ previewId }] = await this.postEditService.updateDraft(postId);
this.res.sendJson({ meta: { previewId } });
const { wantReview: rawWantReview } = this.req.body as PostEditModel;
const wantReview = this.paramsService.convertToBoolNumber(rawWantReview);
if (wantReview) {
await this.emailService.sendDraftToReview(this.postEditService.ownerPubId, postId);
}
}
// ... other methods
}
Even considering that my framework has a Dependency Injection system, it is still quite difficult to write tests without the feature described in this issue. In order to ignore the implementation details of the 10 services listed in the constructor, I need to simulate their results with mocks.
How do I work with these mocks then? As mentioned in this issue, I have to sacrifice type checking:
(postEditController as any).req = { routeParams: { postId: '10' }, body: { authorshipId: 1 } };
And then, to write tests with jest, I also need to bypass type checking:
expect((postEditController as any).res.sendJson.mock.calls.length).toBe(1);
expect((postEditController as any).res.sendJson.mock.calls[0][0]).toEqual({ meta: { previewId } });
As you can see, not only is the PostEditController instance type lost, the types that come with .mock.* are also lost.
There is a safer way to check types, but also a more complex one. In my case, in PostEditController I first make the mentioned properties protected, and then in its mock I make them public.
But obviously, this is a workaround, and developers have to create protected properties where private properties are actually needed.
@KostyaTretyak in your example req is passed as an argument to the controller's constructor. Looks like you can instantiate the controller directly, providing whatever mock implementations you want as arguments. Or you can use your DI container to instantiate it for you. In both cases it's not really required to access the private property of the controller.
Workaround: there is a way to access private fields with myObject["myPrivateField"] syntax.
Workaround: there is a way to access private fields with myObject["myPrivateField"] syntax.
Thats not type safe. If you change your code you wont get error at compile time.
Every major languages have a way to access private/protected methods/variables.
It is nice to avoid it and only interact/test the class using public methods/variables but let's face that it can be a pain in the ash.
For that this feature would give a nice a solution with compile time checking. The programmer's responsibility to test the class properly (as it is always).
I think the strongest reasons for having this feature are more practical than theoretical.
@matepek Actually the someObject["privateField"] access is type safe: Playground link
Cool, didn't know that. I guess it requires some typescript flag to be enabled because I weakly remember trying something like this before. Thanks.
Actually the someObject["privateField"] access is type safe.
But during refactoring (rename property via F2), the value in the expression someObject ["privateField"] will not change (at least in VS Code and in TypeScript's playground).
@ahejlsberg, @DanielRosenwasser, @RyanCavanaugh, please guys, let's at least discuss this issue. Should the number of upvotes be decisive in whether or not to implement this feature? I am deeply convinced that this feature is essential if we want to use the type safe way.
If you believe users can work without this feature, please explain how to achieve this without workarounds. I gave a specific example above. Although I can still transmit mocks via DI, then I won't be able to use one of the most popular frameworks - jest - to control how certain private methods are called.