开云体育

ctrl + shift + ? for shortcuts
© 2025 开云体育

Subclass to Test in JavaScript?


 

Hi, folks. I embarrassed myself in a Surviving Legacy Code training course recently by fumbling with Subclass to Test in front of a bunch of JavaScript programmers. It occurred to me that I don't know the object-based analog to this approach. I waved my hands a little, then thankfully, I ran the clock out without much of a resolution.

I'd like to make it up to them and I need your help.

What is the corresponding technique when working in a language with objects but not classes? In JavaScript, for example, how do I achieve the same effect of being able to create a "testable" (more-inspectable) object in my test with minimal changes to the legacy code object definition? In JavaScript, can I do the equivalent of making a property visible to "subclasses" without making it widely-visible to all legacy clients? Or should I think about it a different way?

My goal is to make minimally-disruptive changes to the existing code, while adding tests for it that would support refactoring. I want the equivalent of overriding _this small part_ so that I can simulate/control it in order to test that other part of the same object.

I feel like I'm missing something obvious, since I'm barely functionally literate in JavaScript.

Thanks.

J. B. Rainsberger :: :: ::

--
J. B. (Joe) Rainsberger :: :: ::
Teaching evolutionary design and TDD since 2002


 

Hi JB,

This is trivially simple in JS, simply create a new object (your "subclass to test") that has its prototype set to your SUT object.

All of your SUT methods and attributes will now be accessible in your child object.

Simples!
---
a = {
? f: (x) => x + 1,
? z: 2
}

b = {}
b.__proto__ = a
console.log(b.z)? ? ?// 2
console.log(b.f(4))? // 5


On Sun, 5 Jun 2022, 13:43 J. B. Rainsberger, <me@...> wrote:
Hi, folks. I embarrassed myself in a Surviving Legacy Code training course recently by fumbling with Subclass to Test in front of a bunch of JavaScript programmers. It occurred to me that I don't know the object-based analog to this approach. I waved my hands a little, then thankfully, I ran the clock out without much of a resolution.

I'd like to make it up to them and I need your help.

What is the corresponding technique when working in a language with objects but not classes? In JavaScript, for example, how do I achieve the same effect of being able to create a "testable" (more-inspectable) object in my test with minimal changes to the legacy code object definition? In JavaScript, can I do the equivalent of making a property visible to "subclasses" without making it widely-visible to all legacy clients? Or should I think about it a different way?

My goal is to make minimally-disruptive changes to the existing code, while adding tests for it that would support refactoring. I want the equivalent of overriding _this small part_ so that I can simulate/control it in order to test that other part of the same object.

I feel like I'm missing something obvious, since I'm barely functionally literate in JavaScript.

Thanks.

J. B. Rainsberger :: :: ::

--
J. B. (Joe) Rainsberger :: :: ::
Teaching evolutionary design and TDD since 2002


 

I don't believe that using the prototype chain quite accomplishes what is being asked for.
"Objects, but not classes" implies the usage of closures to encapsulate data, and I *think* that what is being asked for is to have access to the bits that are "closed" similar to how one might?access protected members in a class in java.
The?closed values are not accessible to "subclasses" on the prototype chain.

I'm imagining that the legacy code you are talking about is structured something like?this:

// a function that returns an object
function Product(sku) {

? // these values are "closed" aka "private"
? let quantity, sku;

? // this is also "private" / closed
? function calculatePrice() {
? ? ?// ... some logic here that we want to either:
? ? ?//? ?1. test directly or?
? ? ?//.? 2. stub to allow us to test other parts of this object
? ? ?// this function's behavior depends on the closed values
? }

? // the object returned
? return {
? ? setQuantity(val) {
? ? ? quantity = val
? ? }.?
? ? getPrice() {
? ? ? calculatePrice()
? ? }
? }
}

In this case, there is not?a good way to get access to the "innards" without modifying the SUT.

On Sun, Jun 5, 2022 at 8:41 AM Sleepyfox <sleepyfox@...> wrote:
Hi JB,

This is trivially simple in JS, simply create a new object (your "subclass to test") that has its prototype set to your SUT object.

All of your SUT methods and attributes will now be accessible in your child object.

Simples!
---
a = {
? f: (x) => x + 1,
? z: 2
}

b = {}
b.__proto__ = a
console.log(b.z)? ? ?// 2
console.log(b.f(4))? // 5

On Sun, 5 Jun 2022, 13:43 J. B. Rainsberger, <me@...> wrote:
Hi, folks. I embarrassed myself in a Surviving Legacy Code training course recently by fumbling with Subclass to Test in front of a bunch of JavaScript programmers. It occurred to me that I don't know the object-based analog to this approach. I waved my hands a little, then thankfully, I ran the clock out without much of a resolution.

I'd like to make it up to them and I need your help.

What is the corresponding technique when working in a language with objects but not classes? In JavaScript, for example, how do I achieve the same effect of being able to create a "testable" (more-inspectable) object in my test with minimal changes to the legacy code object definition? In JavaScript, can I do the equivalent of making a property visible to "subclasses" without making it widely-visible to all legacy clients? Or should I think about it a different way?

My goal is to make minimally-disruptive changes to the existing code, while adding tests for it that would support refactoring. I want the equivalent of overriding _this small part_ so that I can simulate/control it in order to test that other part of the same object.

I feel like I'm missing something obvious, since I'm barely functionally literate in JavaScript.

Thanks.

J. B. Rainsberger :: :: ::

--
J. B. (Joe) Rainsberger :: :: ::
Teaching evolutionary design and TDD since 2002


 

Sadly Dave is right here - the contents of a closure are, well,
closed. Hence the name.

Luckily Crockford's Module Pattern is a rare sight these days, I can
count on the fingers of one mouse the number of times I've seen it in
the wild in the last ten years. These days the focus tends to be far
more on 'faux-O' Javascript, 'Haskell-wannabe Typescript', or some
functional-style code that looks like a mashup of Clojure, Ruby &
Absinthe.

If the Module Pattern _is_ your problem, then a refactoring like
'extract method to helper object' is a reasonable choice, and the path
you'd take would be very similar to that in a typical OO language
environment.

Fox
---

On Sun, 5 Jun 2022 at 17:42, Dave Foley <davidmfoley@...> wrote:

I don't believe that using the prototype chain quite accomplishes what is being asked for.
"Objects, but not classes" implies the usage of closures to encapsulate data, and I *think* that what is being asked for is to have access to the bits that are "closed" similar to how one might access protected members in a class in java.
The closed values are not accessible to "subclasses" on the prototype chain.

I'm imagining that the legacy code you are talking about is structured something like this:

// a function that returns an object
function Product(sku) {

// these values are "closed" aka "private"
let quantity, sku;

// this is also "private" / closed
function calculatePrice() {
// ... some logic here that we want to either:
// 1. test directly or
//. 2. stub to allow us to test other parts of this object
// this function's behavior depends on the closed values
}

// the object returned
return {
setQuantity(val) {
quantity = val
}.
getPrice() {
calculatePrice()
}
}
}

In this case, there is not a good way to get access to the "innards" without modifying the SUT.

On Sun, Jun 5, 2022 at 8:41 AM Sleepyfox <sleepyfox@...> wrote:

Hi JB,

This is trivially simple in JS, simply create a new object (your "subclass to test") that has its prototype set to your SUT object.

All of your SUT methods and attributes will now be accessible in your child object.

Simples!
---
a = {
f: (x) => x + 1,
z: 2
}

b = {}
b.__proto__ = a
console.log(b.z) // 2
console.log(b.f(4)) // 5

On Sun, 5 Jun 2022, 13:43 J. B. Rainsberger, <me@...> wrote:

Hi, folks. I embarrassed myself in a Surviving Legacy Code training course recently by fumbling with Subclass to Test in front of a bunch of JavaScript programmers. It occurred to me that I don't know the object-based analog to this approach. I waved my hands a little, then thankfully, I ran the clock out without much of a resolution.

I'd like to make it up to them and I need your help.

What is the corresponding technique when working in a language with objects but not classes? In JavaScript, for example, how do I achieve the same effect of being able to create a "testable" (more-inspectable) object in my test with minimal changes to the legacy code object definition? In JavaScript, can I do the equivalent of making a property visible to "subclasses" without making it widely-visible to all legacy clients? Or should I think about it a different way?

My goal is to make minimally-disruptive changes to the existing code, while adding tests for it that would support refactoring. I want the equivalent of overriding _this small part_ so that I can simulate/control it in order to test that other part of the same object.

I feel like I'm missing something obvious, since I'm barely functionally literate in JavaScript.

Thanks.

J. B. Rainsberger :: :: ::

--
J. B. (Joe) Rainsberger :: :: ::
Teaching evolutionary design and TDD since 2002


 

I looked up Subclass to Test here,? and I'm still not sure if I'm understanding the use case correctly... However.?

One thing I like about JavaScript is the ability to just replace a function or property on an object with another one. I do this alot with reporting code.?
const oldLog = console.log
console.log = myNewFunction
....
console.log = oldLog?

This lets me do some quick mocking for just a subsection of object without having to replicate everything.?

The other thing we sometimes do is extract a new function which returns the function we are working on, and then allow our outer function to modify or provide some context variables.?

And lastly, we might refactor a module and what it exports and requires to modify dependencies that go in or out.?

On Sun, 5 Jun 2022, 15:42 J. B. Rainsberger, <me@...> wrote:
Hi, folks. I embarrassed myself in a Surviving Legacy Code training course recently by fumbling with Subclass to Test in front of a bunch of JavaScript programmers. It occurred to me that I don't know the object-based analog to this approach. I waved my hands a little, then thankfully, I ran the clock out without much of a resolution.

I'd like to make it up to them and I need your help.

What is the corresponding technique when working in a language with objects but not classes? In JavaScript, for example, how do I achieve the same effect of being able to create a "testable" (more-inspectable) object in my test with minimal changes to the legacy code object definition? In JavaScript, can I do the equivalent of making a property visible to "subclasses" without making it widely-visible to all legacy clients? Or should I think about it a different way?

My goal is to make minimally-disruptive changes to the existing code, while adding tests for it that would support refactoring. I want the equivalent of overriding _this small part_ so that I can simulate/control it in order to test that other part of the same object.

I feel like I'm missing something obvious, since I'm barely functionally literate in JavaScript.

Thanks.

J. B. Rainsberger :: :: ::

--
J. B. (Joe) Rainsberger :: :: ::
Teaching evolutionary design and TDD since 2002


 

On Sun, Jun 5, 2022 at 1:42 PM Dave Foley <davidmfoley@...> wrote:
?
I don't believe that using the prototype chain quite accomplishes what is being asked for.
"Objects, but not classes" implies the usage of closures to encapsulate data, and I *think* that what is being asked for is to have access to the bits that are "closed" similar to how one might?access protected members in a class in java.
The?closed values are not accessible to "subclasses" on the prototype chain.

Indeed, this was the conceptual mistake I made on stage. I was overriding a method that queried a closed variable in the SUT and simultaneously expecting the underlying value to change later. It was a mess and I hope I was just tired. :P

I'm imagining that the legacy code you are talking about is structured something like?this:

// a function that returns an object
function Product(sku) {

? // these values are "closed" aka "private"
? let quantity, sku;

? // this is also "private" / closed
? function calculatePrice() {
? ? ?// ... some logic here that we want to either:
? ? ?//? ?1. test directly or?
? ? ?//.? 2. stub to allow us to test other parts of this object
? ? ?// this function's behavior depends on the closed values
? }

? // the object returned
? return {
? ? setQuantity(val) {
? ? ? quantity = val
? ? }.?
? ? getPrice() {
? ? ? calculatePrice()
? ? }
? }
}

In this case, there is not?a good way to get access to the "innards" without modifying the SUT.

Thank you for confirming this. I think I understand better now.
--
J. B. (Joe) Rainsberger :: ?:: ::


--
J. B. (Joe) Rainsberger :: :: ::
Teaching evolutionary design and TDD since 2002


 

On Tue, Jun 7, 2022 at 5:23 AM Avi Kessner <akessner@...> wrote:
I looked up Subclass to Test here,? and I'm still not sure if I'm understanding the use case correctly... However.?

One thing I like about JavaScript is the ability to just replace a function or property on an object with another one. I do this alot with reporting code.?
const oldLog = console.log
console.log = myNewFunction
....
console.log = oldLog?

This lets me do some quick mocking for just a subsection of object without having to replicate everything.?

This is what I had expected to do, but since I was working with methods that changed the state of variables closed inside the object... I was just very very wrong about the whole thing.
?
The other thing we sometimes do is extract a new function which returns the function we are working on, and then allow our outer function to modify or provide some context variables.?

Ooh. Can you throw together a quick example of that? I'm not _quite_ getting it yet.
--
J. B. (Joe) Rainsberger :: ?:: ::


--
J. B. (Joe) Rainsberger :: :: ::
Teaching evolutionary design and TDD since 2002


 

Here are two techniques for injecting a dependency using the previous example with minimal modification:

1. A function that returns the Product function

// the "real" production price logic
function calculatePrice(sku, quantity) { ... }

// Sort of a "constructor constructor" that allows
// injecting alternate implementation
function makeProduct(calculatePrice) {
? return function Product(sku) {

? ? let quantity, sku;

? ? return {
? ? ? setQuantity(val) {
? ? ? ? quantity = val
? ? ? },
? ? ? getPrice() {
? ? ? ? return calculatePrice(sku, quantity)
? ? ? }
? ? }
? }
}

// in production, use the "real" implementation
const Product = makeProduct(calculatePrice)

// in test, something like:
const TestProduct = makeProduct(calculatePriceTestDouble)

2. Optional argument with default value

function calculatePrice(sku, quantity) {...}

// default argument value that is overridable
function Product(sku, calculator = calculatePrice) {

? let quantity, sku;

? return {
? ? setQuantity(val) {
? ? ? quantity = val
? ? },
? ? getPrice() {
? ? ? return calculator(sku, quantity)
? ? }
? }
}

// in production, the default arg is used
const realProduct = Product('abc-123')

// in test, override
const testProduct = Product('abc-123', calculatePriceTestDouble)


On Fri, Jun 10, 2022 at 5:16 AM J. B. Rainsberger <me@...> wrote:
On Tue, Jun 7, 2022 at 5:23 AM Avi Kessner <akessner@...> wrote:
I looked up Subclass to Test here,? and I'm still not sure if I'm understanding the use case correctly... However.?

One thing I like about JavaScript is the ability to just replace a function or property on an object with another one. I do this alot with reporting code.?
const oldLog = console.log
console.log = myNewFunction
....
console.log = oldLog?

This lets me do some quick mocking for just a subsection of object without having to replicate everything.?

This is what I had expected to do, but since I was working with methods that changed the state of variables closed inside the object... I was just very very wrong about the whole thing.
?
The other thing we sometimes do is extract a new function which returns the function we are working on, and then allow our outer function to modify or provide some context variables.?

Ooh. Can you throw together a quick example of that? I'm not _quite_ getting it yet.
--
J. B. (Joe) Rainsberger :: ?:: ::


--
J. B. (Joe) Rainsberger :: :: ::
Teaching evolutionary design and TDD since 2002