Sadly there is no such feature as it's the TLS handshake failing which is before it should ever hit application logic. However you might be missing the real problem here in all truth and honesty if someone has released code that is not verifying TLS correctly or is actively ignoring TLS errors.
The problem of inexperienced coders not knowing how to test their code

Of course this comic is missing the expert programmer: "Oh it's finally passing all of it's tests. Good."
I think a bigger question is "Why does a developer need a self signed certificate?" and if the answer is "Testing" then there are much better, safer, and easier ways to handle testing that don't require certificates or servers. If a developer cannot figure out how to test code for a server or client application outside of a client and server environment then what is that code doing in the production code base and why is that developer not receiving help to bring them up to faster and safer practices and design patterns?
With code being just that: code; there are infinitely many ways to test it that don't require a server, certificate, or even man hours. They should adopt those ways moving forward, and retroactively integrate those patterns into older code.
"But what if a developer doesn't want to?" is a common argument against this. To that the answer is "Then go find a developer who believes in proper and safe design practices." End. Of. Story. It is not worth the bugs, headaches, and lost time/profit they will introduce with their rampant disregard for standards set in place by those more experienced than themselves they could learn from. It doesn't matter if they've have more years of experience either, because their experience is wrong. It's especially more so since these design practices are free and readily available.
With a load of free resources and proper instruction it's a wonder that anything other than a small inexperienced company would ever run into this situation but it does happen. Even then it's not surprising that many of these companies eventually reach the same point in time where they do adopt these practices.
An example
Now this may seem like I'm just ranting and raving(with would be cathartic) but in all honesty this is an easy pattern to adopt. Lets look at an example for NodeJS route handler
In this example we are going to write a route handler that will log a visitor to the site, and then proceed to the next handler.
handler.js
module.exports = route //export the route for importing and compilation in another file
function route(req, res, next){
req.app.locals.plugins.logVisit(req)
.then(error => error ? next(error) : next())
}
Now we need to test this route. Lets write up a test without any testing library that will output the number of succeeded tests and failed tests and only test this route
test.js
let handler = require('./handler')
let passed = 0;
let failed = 0;
let goodReq = {
app: {
locals: {
plugins: {
logVisit(req){
req != null
? ++passed && console.log('Properly passed the request object for logging')
: ++failed && console.log('Did not pass the request object for logging')
return Promise.resolve()
}
}
}
}
}
let goodNext = function (error){
if (error){
return ++failed && console.log('Improperly handled a good logVisit call')
}
++passed && console.log('Properly handled a good logVisit call')
}
let badReq = {
app:{
locals:{
plugins:{
logVisit(req){
req != null
? ++passed && console.log('Properly passed the request object for logging')
: ++failed && console.log('Did not pass the request object for logging')
return Promise.resolve(new Error('Failed logVisit called'))
}
}
}
}
}
let badNext = function (error){
if (!error){
return ++failed && console.log('Failed handling an error logVisit call')
}
++passed && console.log('Properly handled an errored logVisit call')
}
typeof handler == 'function'
? ++passed && console.log('The handler is a function')
: ++failed && console.log('The handler is not a function')
if (failed) {
throw new Error('The handler is not a function. It cannot be called as such, aborting future tests')
}
console.log('Handler will call logVisit and recieve a non errored state')
handler(goodReq, null, goodNext)
if (failed) {
throw new Error('The handler did not properly call and handle logVisit success. Aborting future tests')
}
console.log('Handler will call logVisit and recieve an errored state')
handler(badReq, null, badNext)
if (failed) {
throw new Error('The handler did not properly call and handle logVisit error. Aborting future tests')
}
setTimeout(() => {
console.log('All tests for the handler passed! It is now ready for integration testing')
console.log('# of Tests: ', (passed + failed))
console.log('Number of failed tests: ', failed)
console.log('Number of passed tests: ', passed)
}, 1000);
As you can see we don't even NEED a server to test the code, or a self signed certificate or even a testing library(although that probably would have made it easier to test). Before this handler would ever go into a server, it would be tested. At that point the developer already knows it works as intended. It doesn't need a self signed certificate. It can also be proven that the tests work by running them.
As long as you have proper testing, you don't need a self signed certificate.