question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unmanaged transactions, CLS and tests

See original GitHub issue

Good day. This is a duplication of https://github.com/sequelize/sequelize/issues/3888 with some extended examples.

version == 3.4.1

So, here is basic setup:

  1. Application, that uses sequelize transactions and create instance of db like:
const createDb = (conf, namespaceName='my-super-personal-namespace') => {
  const {uri} = conf;
  const namespace = cls.createNamespace(cls);

  Sequelize.cls = namespace;

  const options = Object.assign({}, conf); // Sequelize doesn't work with frozen

  const sequelize = new Sequelize(uri, options);
  const Thing = sequelize.import('./thing');

  return {
    sequelize,
    Thing,
    _namespace: namespace
  };
};
  1. And here goes a test:
let db;
let transaction;

before(() => {
  db = createDb(config);

  return db.sequelize.sync();
});

beforeEach(() => db.sequelize
    .transaction({autocommit: false})
    .then(t => {
      transaction = t;
    }));

afterEach(() => transaction.rollback());

after(() => db.sequelize.drop());

it('should create instance of Thing', () => {
  return db.Thing.create({
    name: 'foo'
  }, {transaction}).then(thing => {  // FIXME: bad for tests
    expect(thing.id).to.be.a('number');
    expect(thing.name).to.be.eql('foo');
  });
});

This code works but it requires manual pass of transaction.

I’ve expected that following code would work, but it fails in afterEach:

let db;

before(() => {
  db = createDb(config);

  return db.sequelize.sync();
});

beforeEach(() => db.sequelize
  .transaction({autocommit: false})
  .then(t => {
    db._namespace.set('transaction', t);
  }));

afterEach(() => db._namespace.get('transaction').rollback());

after(() => db.sequelize.drop());

it('should create instance of Thing', () => {
  return db.Thing.create({
    name: 'foo'
  }).then(thing => {
    expect(thing.id).to.be.a('number');
    expect(thing.name).to.be.eql('foo');
  });
});

Could you advice please what is a best way to solve this issue?

Thank you!

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Comments:17 (8 by maintainers)

github_iconTop GitHub Comments

3reactions
janmeiercommented, Jul 25, 2015

If I read your code correctly, this is never going to work with CLS. The transaction is only available in the then callback:

beforeEach(() => db.sequelize
  .transaction({autocommit: false})
  .then(t => {
    db._namespace.set('transaction', t);
    /* ANY FUNCTION CALLED FROM HERE CAN ACCESS t */
  }));

But nothing is done in that callback - after it finished, then context is cleared, because that callback / promise chain ends.

Thing.create should be called from inside the callback:

it('should create instance of Thing', () => {
  return db.sequelize.transaction().then(function (t) {
    db._namespace.set('transaction', t);
    return db.Thing.create({
        name: 'foo'
      }).then(thing => {
        expect(thing.id).to.be.a('number');
        expect(thing.name).to.be.eql('foo');
        return t.rollback();
      });
    });
  });
});

You can also do it slightly more succinctly, by using a managed transaction

return db.sequelize.transaction(function () {
  // No need to call namespace.set here - sequelize takes care of that
});
2reactions
akhomchenkocommented, Jul 26, 2015

Update:

My code with CLS can be done like this and it would work, but it is not so ideal.

let db;
let transaction;

before(() => {
  db = createDb(config);

  return db.sequelize.sync();
});

beforeEach(() => db.sequelize
    .transaction({autocommit: false})
    .then(t => {
      transaction = t;
    }));

afterEach(() => transaction.rollback());

after(() => db.sequelize.drop());

it('should create instance of Thing', () => {
  db._namespace.set('transaction', transaction);

  return db.Thing.create({ // Voila, transaction is binded
    name: 'foo'
  }).then(thing => {
    expect(thing.id).to.be.a('number');
    expect(thing.name).to.be.eql('foo');
  });
});
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to use cls-hooked unmanaged transactions?
I'm writing tests with jest and sequelize and I need to keep my database clean for every test, so I want to set...
Read more >
Use CLS with Sequelize Unmanaged transactions-node.js
[Solved]-Use CLS with Sequelize Unmanaged transactions-node.js ... This is tricky for tests because describe() calls can be nested and each can have their ......
Read more >
Transactions - Sequelize
Sequelize supports two ways of using transactions: Unmanaged transactions: Committing and rolling back the transaction should be done manually by the user ...
Read more >
Managed transaction (auto-callback) - Manual | Sequelize
The key difference is that the managed transaction uses a callback that expects a promise to be returned to it while the unmanaged...
Read more >
Handling Nested Db Transactions using CLS - Medium
One of the ways we could solve this is bypassing the Transaction Object from parent to child. We can then use the same...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found