EF core測試時循環參考問題解決

  • 258
  • 0

資料表之間有關聯時
用EF core產生出的entity會各自有對方的class
測試時使用AutoFixture建立mockobject會拋出exception
這篇會說明怎麼解決

先建立兩個class
Class跟Student
有互相關聯
 

public class Student
    {
        public virtual Class Class { get; set; }
        public int ClassId { get; set; }
        public string Name { get; set; }
        public int StudentId { get; set; }
    }
public class Class
    {
        public Class()
        {
            this.Students = new HashSet<Student>();
        }

        public int ClassId { get; set; }
        public DateTime ClassTime { get; set; }
        public virtual ICollection<Student> Students { get; set; }
        public string TeacherName { get; set; }
    }

在測試專案要測試GetClass的方法時
會用autofixture建立假資料

 var fixture = new Fixture();
 var classdtos = fixture.Build<Class>()
                        .CreateMany(10);

但是這一段會拋出例外錯誤
告訴你要使用微軟提供的方法,他會忽略循環參考

  Test method TestCircularReference.Tests.ClassServiceTests.Get_回傳一筆Class threw exception: 
    AutoFixture.ObjectCreationExceptionWithPath: AutoFixture was unable to create an instance of type AutoFixture.Kernel.SeededRequest because the traversed object graph contains a circular reference. Information about the circular path follows below. This is the correct behavior when a Fixture is equipped with a ThrowingRecursionBehavior, which is the default. This ensures that you are being made aware of circular references in your code. Your first reaction should be to redesign your API in order to get rid of all circular references. However, if this is not possible (most likely because parts or all of the API is delivered by a third party), you can replace this default behavior with a different behavior: on the Fixture instance, remove the ThrowingRecursionBehavior from Fixture.Behaviors, and instead add an instance of OmitOnRecursionBehavior:
    
    fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
        .ForEach(b => fixture.Behaviors.Remove(b));
    fixture.Behaviors.Add(new OmitOnRecursionBehavior());

於是new Fixture()這段會調整成

public Fixture CreateOmitOnRecursionFixture()
        {
            var fixture = new Fixture();
            fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
                .ForEach(b => fixture.Behaviors.Remove(b));
            fixture.Behaviors.Add(new OmitOnRecursionBehavior());

            return fixture;
        }

 

但是建出來的model還是包含第二層的class
這在建IEnumerable物件的時候會造成效能不佳
因為通常測試只會測第一層的Class
這邊提供兩種改法
你可以在第一種方法下用WithOut()
把第二層的class捨棄,產生出來會是null
缺點是關聯的資料表比較多時要寫好幾次
 

var fixture = new Fixture();
var classdtos = fixture.Build<Class>()
                .Without(c=>c.Students)
                .CreateMany(10);

或是用.OmitAutoProperties()
產出來的物件都會是預設值
 

var fixture = new Fixture();
var classdtos = fixture.Build<Class>()
                .OmitAutoProperties()
                .CreateMany(10);

 

以上就提供這兩種方法使用