I would like to use TypeScript @Decorators in my unit tests

Before being almost exclusively a frontend developer who only uses JavaScript on a daily basis, I have done quite a bit of Java development on frontend as well as on the backend side. When writing unit tests in Java, our typical unit tests looked like this:

@RunWith(MockitoJUnitRunner.class)
public class StatisticsContextControllerTest {

	@Spy
	@InjectMocks
	private StatisticsContextController beanToTest;
	
	@Mock
	private StatisticsContextBusinessBean statisticsContextBusinessBean;
	
	@Spy
	private StatisticsContextFilter filterSpy = new StatisticsContextFilter();
	
	@Before
	public void setup() {}
	
	@Test
	public void testDenyStatisticsContext() {
	  //
	}
}

This is a JUnit4 test class that is run with Mockito as a test runner. What makes Mockito so amazing is, that I as a developer don’t have to manually handle the mock classes anymore. Mockito does that for me.

In the above test class, Java annotations are used to mark certain properties in the test class as Mocks or Spies that are automatically used to be injected in the to be tested class. This class needs to be marked with the @InjectMocks annotation. Mockito automatically creates mock instances of all mocks, spies and the class that is to be tested. If the class that is to be tested has a property with a type that is the same as one of the mocks, the mock is assigned to this property upon creation of the class.

In the end I have clean code in the test class which doesn’t contain any boiler plate code to setup the test case. I only need to care about mock implementations or mock return values.

And I would really like to use something like this in my unit tests which I write in Typescript. But this is isn’t possible as far as I know unfortunately.

Why?

First, Typescript decorators are still marked as an experimental feature even if they are used extensively in an angular environment. You still have to manually activate them in tsconfig.json via “experimentalDecorators: true”.

Second, the current TypeScript decorators can ONLY be used in classes, such as class decorators or method decorators. But the major testing frameworks (Jasmine, Jest, Mocha) can’t be written as test classes as all of them use a functional approach to define test cases.

This is a typical Jest test case:

describe("MyService", () => {
  let service: myService;

  beforeEach(() => {
    service = new MyService();
  };

  it("should do something", () => {
    service.doSomething();
  });

});

describe(), beforeEach() and it() are “just” functions which are used to define a test suite. Every test case is “only” a function that is called by the testing framework. This means, you can’t decorate any member, variable or function inside the test case with decorators.

I have created quite a few tests in the last weeks and quickly the most tedious and boring thing to do was to define and create the mocks and let them be injected into the class to be tested.

This often looked like something like this:

describe('MyService', () => {
  const errorServiceMock = {
    log: jest.fn().mockName("errorService.log"),
    logError: jest.fn().mockName("errorService.logError")
  };
  let service: MyService;

  beforeEach(() => {
    service = new MyService(errorServiceMock as any);
    
    errorServiceMock.log.clearMock();
    errorServiceMock.logError.clearMock();
  });

This can get quite messy when you use the angular dependency injection extensively and have to declare and define every mock yourself … again and again in every test suite. My test suites often had more lines of code for initializing the mocks than the real test cases.

Jest (the testing framework) has a feature called “module mocking” which mocks a complete module that is imported into your production code, but this doesn’t work for the angular dependency injection as you need objects/class instances to inject into your production code.

After a while I had enough, I didn’t care about the implementation details of the injected classes in my test suite for “myService” and I didn’t want to declare the same mocks again and again. They have their own test suites, I just want to test “myService” and ideally only use mocks to inject into “myService” so I can only define mock implementations and mock return values which are specific to this single test suite.

So, I created a function which did most of the heavy lifting to initialize an object so that it had the same (mocked) public properties as a class that can be parametrized:

describe('MyService', () => {
  const errorServiceMock = mockClass<ErrorService>(ErrorService);

  let service: MyService;

  beforeEach(() => {
    service = new MyService(errorServiceMock);
    
    clearMocks();  // mocks are reused between test cases and suites and need to be cleared
  });

This looked already much better and is type safe too as errorServiceMock has the same members as the ErrorService class. So it can be used as a (injection) parameter for the service class that I want to test.

But as I now had a pure function to define a mock “class” it would be neat to use this function as a decorator. A decorator is just this: a function that is called at a certain point in the lifetime of a class.

Possible solution

In the end I only came to this solution:

describe('MyService', () => {
  class Mocks extends MockHolder {
    @Mock(ErrorService)
    errorService: ErrorService;
  }

  const mocks = initMocks("mocks", Mocks);

  let service: MyService;

  beforeEach(() => {
    service = new MyService(mocks.errorService);
    clearMocks();  // mocks are reused between test cases and suites and need to be cleared
  });

Its just an empty class with some properties that are marked as mocks. The class needs to be initialized at one point and just holds the mocks. The class itself is only a holder of instances of mocks.

This is not much better than using mockClass() directly (see above) and I have not introduced it in my test suites. I’m no real expert in the Javascript testing community and have no clues if any testing framework has discussed plans so that developers are able to use classes as test suites. But it would be nice.

Download

My mockClass() function and the decorator can be found as a gist on github.

Leave a Reply

Your email address will not be published. Required fields are marked *

*