1

I'm currently trying to write some test with jest (newbie). Regarding to my latest post, I've got the following situation:

I've got multiple price fields. Each price field has its own renderer. A renderer has a reference to its pricefield. A pricefield can determine some html snippet from the dom.

// priceField.js

class PriceField {
  constructor() {
    const renderer = new PriceFieldRenderer(this);
    // do something with renderer
  }

  /**
   * Determines Node in DOM for this price field
   */
  determinePriceFieldNode() {
    return document.querySelector(".price-field");
  }
}

export default PriceField;

// priceFieldRenderer.js

class PriceFieldRenderer {
  constructor(priceField) {
    this.priceFieldNode = priceField.determinePriceFieldNode();
  }
}

export default PriceFieldRenderer;

Now I want to test PriceFieldRenderer (with jest). Therefore, I need to mock PriceField and its determinePriceFieldNode() function.

However, I'm not able to pass a mocked price field instance as constructor argument. I've read the official documentation with its sound-player example and googled about 2 hours. Also i tested different implementations, but I'm not able to solve it. Here is one code snippet which throws following error: TypeError: priceField.determinePriceFieldNode is not a function


jest.mock("./priceField");

import PriceFieldRenderer from "./priceFieldRenderer";
import PriceField from "./priceField";

describe("Price field renderer", () => {
  test("with calculated price", () => {
    const priceField = PriceField.mockImplementation(() => {
      return {
        determinePriceFieldNode: () => "<html/>"
      };
    });

    const sut = new PriceFieldRenderer(priceField);

    expect(sut).toBeDefined();
  });
});

I'm sure the way I did is not the best and there is some help out there :)

Rabhi salim
  • 486
  • 5
  • 17
Theiaz
  • 568
  • 6
  • 24

1 Answers1

2

The problem here is that priceField is lowercase and makes an assumption that it's an instance, while it's spy function that should create an instance. It can be fixed with:

PriceField.mockImplementation(...);
const priceField = new PriceField();
const sut = new PriceFieldRenderer(priceField);

priceField module mock and and associated PriceField spy serve no good purpose. They would be needed if it were imported and used in another module, this is the said example from Jest documentation shows.

That PriceFieldRenderer uses dependency injection makes testing simpler, this is one of benefits of the pattern:

const priceField = { determinePriceFieldNode: jest.fn(() => "<html/>") });
const sut = new PriceFieldRenderer(priceField);
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Thanks for the quick answer. I came across a similiar solution regarding to following [post](https://stackoverflow.com/a/50108368/4065938): `PriceField.prototype.determinePriceFieldNode = jest.fn().mockImplementation(...)` However, your hint with the depencey injection is pretty nice! – Theiaz Nov 26 '20 at 14:17
  • 1
    You're welcome. FWIW, `PriceField.prototype.determinePriceFieldNode = jest.fn()` is an antipattern, it prevents a method from being restored, `jest.spyOn` should be used on existing objects as a rule of thumb. – Estus Flask Nov 26 '20 at 14:30